aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contracts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/contracts')
-rw-r--r--packages/contracts/package.json8
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/AssetProxyDispatcher.sol86
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/IAssetProxy.sol34
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/IAssetProxyDispatcher.sol51
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/proxies/ERC20TransferProxy.sol80
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/proxies/ERC20TransferProxy_v1.sol89
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/proxies/ERC721TransferProxy.sol94
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol7
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/IExchange.sol5
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/LibOrder.sol10
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/LibPartialAmount.sol2
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlementProxy.sol77
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol228
-rw-r--r--packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol46
-rw-r--r--packages/contracts/src/contracts/current/utils/Authorizable/Authorizable.sol109
-rw-r--r--packages/contracts/src/contracts/current/utils/Authorizable/IAuthorizable.sol53
-rw-r--r--packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol126
-rw-r--r--packages/contracts/src/contracts/current/utils/Ownable/IOwnable.sol13
-rw-r--r--packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol4
-rw-r--r--packages/contracts/src/utils/asset_proxy_utils.ts67
-rw-r--r--packages/contracts/src/utils/constants.ts1
-rw-r--r--packages/contracts/src/utils/crypto.ts2
-rw-r--r--packages/contracts/src/utils/order_utils.ts6
-rw-r--r--packages/contracts/src/utils/types.ts20
-rw-r--r--packages/contracts/test/asset_proxy_dispatcher/dispatcher.ts339
-rw-r--r--packages/contracts/test/asset_proxy_dispatcher/proxies.ts407
-rw-r--r--packages/contracts/test/exchange/core.ts503
-rw-r--r--packages/contracts/test/exchange/helpers.ts47
-rw-r--r--packages/contracts/test/exchange/wrapper.ts90
29 files changed, 2459 insertions, 145 deletions
diff --git a/packages/contracts/package.json b/packages/contracts/package.json
index 78d50d888..be65787d5 100644
--- a/packages/contracts/package.json
+++ b/packages/contracts/package.json
@@ -26,8 +26,9 @@
"test:circleci": "yarn test:coverage"
},
"config": {
- "abis": "../migrations/src/artifacts/@(DummyToken|TokenTransferProxy|Exchange|TokenRegistry|MultiSigWallet|MultiSigWalletWithTimeLock|MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress|TokenRegistry|ZRXToken).json",
- "contracts": "Exchange,DummyToken,ZRXToken,Token,WETH9,TokenTransferProxy,MultiSigWallet,MultiSigWalletWithTimeLock,MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress,MaliciousToken,TokenRegistry"
+ "abis": "../migrations/src/artifacts/@(DummyToken|TokenTransferProxy|Exchange|TokenRegistry|MultiSigWallet|MultiSigWalletWithTimeLock|MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress|TokenRegistry|ZRXToken|AssetProxyDispatcher|ERC20TransferProxy_v1|ERC20TransferProxy|ERC721TransferProxy|DummyERC721Token).json",
+ "contracts": "Exchange,DummyToken,ZRXToken,Token,WETH9,TokenTransferProxy,MultiSigWallet,MultiSigWalletWithTimeLock,MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress,MaliciousToken,TokenRegistry,AssetProxyDispatcher,ERC20TransferProxy_v1,ERC20TransferProxy,ERC721TransferProxy,DummyERC721Token",
+ "dirs": "src/contracts,zeppelin:../../node_modules/zeppelin-solidity"
},
"repository": {
"type": "git",
@@ -71,6 +72,7 @@
"ethereumjs-util": "^5.1.1",
"ethers-contracts": "^2.2.1",
"lodash": "^4.17.4",
- "web3": "^0.20.0"
+ "web3": "^0.20.0",
+ "zeppelin-solidity": "^1.8.0"
}
}
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/AssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/AssetProxyDispatcher.sol
new file mode 100644
index 000000000..cce330818
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/AssetProxyDispatcher.sol
@@ -0,0 +1,86 @@
+/*
+
+ 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.21;
+
+import "./IAssetProxyDispatcher.sol";
+import "./IAssetProxy.sol";
+import "../../utils/Ownable/Ownable.sol";
+import "../../utils/Authorizable/Authorizable.sol";
+
+contract AssetProxyDispatcher is
+ Ownable,
+ Authorizable,
+ IAssetProxyDispatcher
+{
+ // Mapping from Asset Proxy Id's to their respective Asset Proxy
+ mapping (uint8 => IAssetProxy) public assetProxies;
+
+ /// @dev Delegates transfer to the corresponding asset proxy.
+ /// @param assetMetadata Byte array encoded for the respective asset proxy.
+ /// @param from Address to transfer token from.
+ /// @param to Address to transfer token to.
+ /// @param amount Amount of token to transfer.
+ function transferFrom(
+ bytes assetMetadata,
+ address from,
+ address to,
+ uint256 amount)
+ public
+ onlyAuthorized
+ {
+ // Lookup asset proxy
+ require(assetMetadata.length >= 1);
+ uint8 assetProxyId = uint8(assetMetadata[0]);
+ IAssetProxy assetProxy = assetProxies[assetProxyId];
+
+ // Dispatch transfer to asset proxy
+ // transferFrom will either succeed or throw.
+ assetProxy.transferFrom(assetMetadata, from, to, amount);
+ }
+
+ /// @dev Registers a new asset proxy.
+ /// @param assetProxyId Id of the asset proxy.
+ /// @param newAssetProxyAddress Address of the asset proxy contract to register.
+ /// @param currentAssetProxyAddress Address of existing asset proxy to overwrite.
+ function setAssetProxy(
+ uint8 assetProxyId,
+ address newAssetProxyAddress,
+ address currentAssetProxyAddress)
+ public
+ onlyOwner
+ {
+ // Ensure any existing asset proxy is not unintentionally overwritten
+ require(currentAssetProxyAddress == address(assetProxies[assetProxyId]));
+
+ // Store asset proxy and log registration
+ assetProxies[assetProxyId] = IAssetProxy(newAssetProxyAddress);
+ emit AssetProxyChanged(assetProxyId, newAssetProxyAddress, currentAssetProxyAddress);
+ }
+
+ /// @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)
+ public view
+ returns (IAssetProxy)
+ {
+ IAssetProxy assetProxy = assetProxies[assetProxyId];
+ return assetProxy;
+ }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/IAssetProxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/IAssetProxy.sol
new file mode 100644
index 000000000..5c5f7e605
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/IAssetProxy.sol
@@ -0,0 +1,34 @@
+/*
+
+ 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.21;
+
+contract IAssetProxy {
+
+ /// @dev Transfers assets. Either succeeds or throws.
+ /// @param assetMetadata Byte array encoded for the respective asset proxy.
+ /// @param from Address to transfer token from.
+ /// @param to Address to transfer token to.
+ /// @param amount Amount of token to transfer.
+ function transferFrom(
+ bytes assetMetadata,
+ address from,
+ address to,
+ uint256 amount)
+ public;
+}
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/IAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/IAssetProxyDispatcher.sol
new file mode 100644
index 000000000..9fe7b49a3
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/IAssetProxyDispatcher.sol
@@ -0,0 +1,51 @@
+/*
+
+ 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.21;
+
+import "./IAssetProxy.sol";
+import "../../utils/Authorizable/IAuthorizable.sol";
+
+contract IAssetProxyDispatcher is
+ IAuthorizable,
+ IAssetProxy
+{
+ // Logs registration of new asset proxy
+ event AssetProxyChanged(
+ uint8 id,
+ address newAssetClassAddress,
+ address oldAssetClassAddress
+ );
+
+ /// @dev Sets a new asset proxy.
+ /// @param assetProxyId Id of the asset proxy.
+ /// @param newAssetProxyAddress Address of the asset proxy contract to register.
+ /// @param currentAssetProxyAddress Address of existing asset proxy to overwrite.
+ function setAssetProxy(
+ uint8 assetProxyId,
+ address newAssetProxyAddress,
+ address currentAssetProxyAddress)
+ public;
+
+ /// @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)
+ public view
+ returns (IAssetProxy);
+}
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/proxies/ERC20TransferProxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/proxies/ERC20TransferProxy.sol
new file mode 100644
index 000000000..61fcd9d00
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/proxies/ERC20TransferProxy.sol
@@ -0,0 +1,80 @@
+/*
+
+ 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.21;
+
+import "../IAssetProxy.sol";
+import "../../../utils/LibBytes/LibBytes.sol";
+import "../../../utils/Authorizable/Authorizable.sol";
+import { Token_v1 as ERC20Token } from "../../../../previous/Token/Token_v1.sol";
+
+contract ERC20TransferProxy is
+ LibBytes,
+ Authorizable,
+ IAssetProxy
+{
+
+ /// @dev Transfers ERC20 tokens.
+ /// @param assetMetadata Byte array encoded for the respective asset proxy.
+ /// @param from Address to transfer token from.
+ /// @param to Address to transfer token to.
+ /// @param amount Amount of token to transfer.
+ function transferFrom(
+ bytes assetMetadata,
+ address from,
+ address to,
+ uint256 amount)
+ public
+ onlyAuthorized
+ {
+ address token = decodeMetadata(assetMetadata);
+ bool success = ERC20Token(token).transferFrom(from, to, amount);
+ require(success == true);
+ }
+
+ /// @dev Encodes ERC20 byte array for the ERC20 asset proxy.
+ /// @param assetProxyId Id of the asset proxy.
+ /// @param tokenAddress Address of the asset.
+ /// @return assetMetadata Byte array encoded for the ERC20 asset proxy.
+ function encodeMetadata(
+ uint8 assetProxyId,
+ address tokenAddress)
+ public pure
+ returns (bytes assetMetadata)
+ {
+ // 0 is reserved as invalid proxy id
+ require(assetProxyId != 0);
+
+ // Encode fields into a byte array
+ assetMetadata = new bytes(21);
+ assetMetadata[0] = byte(assetProxyId);
+ writeAddress(tokenAddress, assetMetadata, 1);
+ return assetMetadata;
+ }
+
+ /// @dev Decodes ERC20-encoded byte array for the ERC20 asset proxy.
+ /// @param assetMetadata Byte array encoded for the ERC20 asset proxy.
+ /// @return tokenAddress Address of ERC20 token.
+ function decodeMetadata(bytes assetMetadata)
+ public pure
+ returns (address tokenAddress)
+ {
+ require(assetMetadata.length == 21);
+ return readAddress(assetMetadata, 1);
+ }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/proxies/ERC20TransferProxy_v1.sol b/packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/proxies/ERC20TransferProxy_v1.sol
new file mode 100644
index 000000000..6b19f1a52
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/proxies/ERC20TransferProxy_v1.sol
@@ -0,0 +1,89 @@
+/*
+
+ 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.21;
+
+import "../IAssetProxy.sol";
+import "../../../utils/LibBytes/LibBytes.sol";
+import "../../TokenTransferProxy/ITokenTransferProxy.sol";
+import "../../../utils/Authorizable/Authorizable.sol";
+
+contract ERC20TransferProxy_v1 is
+ LibBytes,
+ Authorizable,
+ IAssetProxy
+{
+ ITokenTransferProxy TRANSFER_PROXY;
+
+ /// @dev Contract constructor.
+ /// @param tokenTransferProxyContract erc20 token transfer proxy contract.
+ function ERC20TransferProxy_v1(ITokenTransferProxy tokenTransferProxyContract)
+ public
+ {
+ TRANSFER_PROXY = tokenTransferProxyContract;
+ }
+
+ /// @dev Transfers ERC20 tokens.
+ /// @param assetMetadata Byte array encoded for the respective asset proxy.
+ /// @param from Address to transfer token from.
+ /// @param to Address to transfer token to.
+ /// @param amount Amount of token to transfer.
+ function transferFrom(
+ bytes assetMetadata,
+ address from,
+ address to,
+ uint256 amount)
+ public
+ onlyAuthorized
+ {
+ address token = decodeMetadata(assetMetadata);
+ bool success = TRANSFER_PROXY.transferFrom(token, from, to, amount);
+ require(success == true);
+ }
+
+ /// @dev Encodes ERC20 byte array for the ERC20 asset proxy.
+ /// @param assetProxyId Id of the asset proxy.
+ /// @param tokenAddress Address of the asset.
+ /// @return assetMetadata Byte array encoded for the ERC20 asset proxy.
+ function encodeMetadata(
+ uint8 assetProxyId,
+ address tokenAddress)
+ public pure
+ returns (bytes assetMetadata)
+ {
+ // 0 is reserved as invalid proxy id
+ require(assetProxyId != 0);
+
+ // Encode fields into a byte array
+ assetMetadata = new bytes(21);
+ assetMetadata[0] = byte(assetProxyId);
+ writeAddress(tokenAddress, assetMetadata, 1);
+ return assetMetadata;
+ }
+
+ /// @dev Decodes ERC20-encoded byte array for the ERC20 asset proxy.
+ /// @param assetMetadata Byte array encoded for the ERC20 asset proxy.
+ /// @return tokenAddress Address of ERC20 token.
+ function decodeMetadata(bytes assetMetadata)
+ public pure
+ returns (address tokenAddress)
+ {
+ require(assetMetadata.length == 21);
+ return readAddress(assetMetadata, 1);
+ }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/proxies/ERC721TransferProxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/proxies/ERC721TransferProxy.sol
new file mode 100644
index 000000000..51f027a56
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxyDispatcher/proxies/ERC721TransferProxy.sol
@@ -0,0 +1,94 @@
+/*
+
+ 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.21;
+
+import "../IAssetProxy.sol";
+import "../../../utils/LibBytes/LibBytes.sol";
+import "../../../utils/Authorizable/Authorizable.sol";
+import "/zeppelin/contracts/token/ERC721/ERC721Token.sol";
+
+
+contract ERC721TransferProxy is
+ LibBytes,
+ Authorizable,
+ IAssetProxy
+{
+
+ /// @dev Transfers ERC20 tokens.
+ /// @param assetMetadata Byte array encoded for the respective asset proxy.
+ /// @param from Address to transfer token from.
+ /// @param to Address to transfer token to.
+ /// @param amount Amount of token to transfer.
+ function transferFrom(
+ bytes assetMetadata,
+ address from,
+ address to,
+ uint256 amount)
+ public
+ onlyAuthorized
+ {
+ // Decode metadata
+ address token;
+ uint256 tokenId;
+ (token, tokenId) = decodeMetadata(assetMetadata);
+
+ // There exists only 1 of each token.
+ require(amount == 1);
+
+ // Call ERC721 contract. Either succeeds or throws.
+ ERC721Token(token).transferFrom(from, to, tokenId);
+ }
+
+ /// @dev Encodes ERC721 byte array for the ERC20 asset proxy.
+ /// @param assetProxyId Id of the asset proxy.
+ /// @param tokenAddress Address of the asset.
+ /// @param tokenId Id of ERC721 token.
+ /// @return assetMetadata Byte array encoded for the ERC721 asset proxy.
+ function encodeMetadata(
+ uint8 assetProxyId,
+ address tokenAddress,
+ uint256 tokenId)
+ public pure
+ returns (bytes assetMetadata)
+ {
+ // 0 is reserved as invalid proxy id
+ require(assetProxyId != 0);
+
+ // Encode fields into a byte array
+ assetMetadata = new bytes(53);
+ assetMetadata[0] = byte(assetProxyId);
+ writeAddress(tokenAddress, assetMetadata, 1);
+ writeUint256(tokenId, assetMetadata, 21);
+ return assetMetadata;
+ }
+
+ /// @dev Decodes ERC721-encoded byte array for the ERC721 asset proxy.
+ /// @param assetMetadata Byte array encoded for the ERC721 asset proxy.
+ /// @return tokenAddress Address of ERC721 token.
+ /// @return tokenId Id of ERC721 token.
+ function decodeMetadata(bytes assetMetadata)
+ public pure
+ returns (address tokenAddress, uint256 tokenId)
+ {
+ require(assetMetadata.length == 53);
+ tokenAddress = readAddress(assetMetadata, 1);
+ tokenId = readUint256(assetMetadata, 21);
+ return (tokenAddress, tokenId);
+ }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol b/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol
index 7a705a0ee..13623894a 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol
@@ -23,6 +23,8 @@ import "./MixinExchangeCore.sol";
import "./MixinSignatureValidator.sol";
import "./MixinSettlementProxy.sol";
import "./MixinWrapperFunctions.sol";
+import "../AssetProxyDispatcher/IAssetProxyDispatcher.sol";
+import "../TokenTransferProxy/ITokenTransferProxy.sol";
contract Exchange is
MixinExchangeCore,
@@ -34,11 +36,12 @@ contract Exchange is
function Exchange(
IToken _zrxToken,
- ITokenTransferProxy _tokenTransferProxy)
+ bytes _zrxProxyMetadata,
+ IAssetProxyDispatcher _assetProxyDispatcher)
public
MixinExchangeCore()
MixinSignatureValidator()
- MixinSettlementProxy(_tokenTransferProxy, _zrxToken)
+ MixinSettlementProxy(_assetProxyDispatcher, _zrxToken, _zrxProxyMetadata)
MixinWrapperFunctions()
{}
}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/IExchange.sol b/packages/contracts/src/contracts/current/protocol/Exchange/IExchange.sol
index b7164a4e9..3315e270f 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/IExchange.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/IExchange.sol
@@ -54,11 +54,6 @@ contract IExchange {
bytes32 indexed orderHash
);
- event LogCancelBefore(
- address indexed maker,
- uint256 salt
- );
-
function ZRX_TOKEN_CONTRACT()
public view
returns (address);
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/LibOrder.sol b/packages/contracts/src/contracts/current/protocol/Exchange/LibOrder.sol
index 5562e692e..759619fc4 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/LibOrder.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/LibOrder.sol
@@ -33,7 +33,9 @@ contract LibOrder {
"uint256 makerFee",
"uint256 takerFee",
"uint256 expirationTimeSeconds",
- "uint256 salt"
+ "uint256 salt",
+ "bytes makerAssetProxyData",
+ "bytes takerAssetProxyData"
);
struct Order {
@@ -48,6 +50,8 @@ contract LibOrder {
uint256 takerFee;
uint256 expirationTimeSeconds;
uint256 salt;
+ bytes makerAssetProxyData;
+ bytes takerAssetProxyData;
}
/// @dev Calculates Keccak-256 hash of the order.
@@ -73,7 +77,9 @@ contract LibOrder {
order.makerFee,
order.takerFee,
order.expirationTimeSeconds,
- order.salt
+ order.salt,
+ order.makerAssetProxyData,
+ order.takerAssetProxyData
)
);
return orderHash;
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/LibPartialAmount.sol b/packages/contracts/src/contracts/current/protocol/Exchange/LibPartialAmount.sol
index 0cf636840..2ff244e98 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/LibPartialAmount.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/LibPartialAmount.sol
@@ -36,5 +36,3 @@ contract LibPartialAmount is SafeMath {
return partialAmount;
}
}
-
-
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlementProxy.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlementProxy.sol
index 2d7e98184..be5fec96a 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlementProxy.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlementProxy.sol
@@ -20,22 +20,22 @@ pragma solidity ^0.4.21;
pragma experimental ABIEncoderV2;
import "./mixins/MSettlement.sol";
-import "../TokenTransferProxy/ITokenTransferProxy.sol";
import "../../tokens/Token/IToken.sol";
import "./LibPartialAmount.sol";
+import "../AssetProxyDispatcher/IAssetProxyDispatcher.sol";
/// @dev Provides MixinSettlement
contract MixinSettlementProxy is
MSettlement,
LibPartialAmount
{
-
- ITokenTransferProxy TRANSFER_PROXY;
+ IAssetProxyDispatcher TRANSFER_PROXY;
+ bytes ZRX_PROXY_METADATA;
IToken ZRX_TOKEN;
function transferProxy()
- external view
- returns (ITokenTransferProxy)
+ public view
+ returns (IAssetProxyDispatcher)
{
return TRANSFER_PROXY;
}
@@ -47,15 +47,26 @@ contract MixinSettlementProxy is
return ZRX_TOKEN;
}
+ function zrxProxyMetadata()
+ external view
+ returns (bytes)
+ {
+ return ZRX_PROXY_METADATA;
+ }
+
function MixinSettlementProxy(
- ITokenTransferProxy _proxyContract,
- IToken _zrxToken)
+ IAssetProxyDispatcher assetProxyDispatcherContract,
+ IToken zrxToken,
+ bytes zrxProxyMetadata)
public
{
- ZRX_TOKEN = _zrxToken;
- TRANSFER_PROXY = _proxyContract;
+ ZRX_TOKEN = zrxToken;
+ TRANSFER_PROXY = assetProxyDispatcherContract;
+ ZRX_PROXY_METADATA = zrxProxyMetadata;
}
+
+
function settleOrder(
Order memory order,
address takerAddress,
@@ -68,43 +79,35 @@ contract MixinSettlementProxy is
)
{
makerTokenFilledAmount = getPartialAmount(takerTokenFilledAmount, order.takerTokenAmount, order.makerTokenAmount);
- require(
- TRANSFER_PROXY.transferFrom(
- order.makerTokenAddress,
- order.makerAddress,
- takerAddress,
- makerTokenFilledAmount
- )
+ TRANSFER_PROXY.transferFrom(
+ order.makerAssetProxyData,
+ order.makerAddress,
+ takerAddress,
+ makerTokenFilledAmount
);
- require(
- TRANSFER_PROXY.transferFrom(
- order.takerTokenAddress,
- takerAddress,
- order.makerAddress,
- takerTokenFilledAmount
- )
+ TRANSFER_PROXY.transferFrom(
+ order.takerAssetProxyData,
+ takerAddress,
+ order.makerAddress,
+ takerTokenFilledAmount
);
if (order.feeRecipientAddress != address(0)) {
if (order.makerFee > 0) {
makerFeePaid = getPartialAmount(takerTokenFilledAmount, order.takerTokenAmount, order.makerFee);
- require(
- TRANSFER_PROXY.transferFrom(
- ZRX_TOKEN,
- order.makerAddress,
- order.feeRecipientAddress,
- makerFeePaid
- )
+ TRANSFER_PROXY.transferFrom(
+ ZRX_PROXY_METADATA,
+ order.makerAddress,
+ order.feeRecipientAddress,
+ makerFeePaid
);
}
if (order.takerFee > 0) {
takerFeePaid = getPartialAmount(takerTokenFilledAmount, order.takerTokenAmount, order.takerFee);
- require(
- TRANSFER_PROXY.transferFrom(
- ZRX_TOKEN,
- takerAddress,
- order.feeRecipientAddress,
- takerFeePaid
- )
+ TRANSFER_PROXY.transferFrom(
+ ZRX_PROXY_METADATA,
+ takerAddress,
+ order.feeRecipientAddress,
+ takerFeePaid
);
}
}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol
index 8f52043c4..fdc906076 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol
@@ -65,76 +65,163 @@ contract MixinWrapperFunctions is
// We need to call MExchangeCore.fillOrder using a delegatecall in
// assembly so that we can intercept a call that throws. For this, we
// need the input encoded in memory in the Ethereum ABIv2 format [1].
-
- // | Offset | Length | Contents |
- // |--------|---------|------------------------------|
- // | 0 | 4 | function selector |
- // | 4 | 11 * 32 | Order order |
- // | 356 | 32 | uint256 takerTokenFillAmount |
- // | 388 | 32 | offset to signature (416) |
- // | 420 | 32 | len(signature) |
- // | 452 | (1) | signature |
- // | (2) | (3) | padding (zero) |
- // | (4) | | end of input |
-
- // (1): len(signature)
- // (2): 452 + len(signature)
- // (3): (32 - len(signature)) mod 32
- // (4): 452 + len(signature) + (32 - len(signature)) mod 32
-
+
+ // | Area | Offset | Length | Contents |
+ // | -------- |--------|---------|-------------------------------------------- |
+ // | Header | 0x00 | 4 | function selector |
+ // | Params | | 3 * 32 | function parameters: |
+ // | | 0x00 | | 1. offset to order (*) |
+ // | | 0x20 | | 2. takerTokenFillAmount |
+ // | | 0x40 | | 3. offset to signature (*) |
+ // | Data | | 13 * 32 | order: |
+ // | | 0x000 | | 1. makerAddress |
+ // | | 0x020 | | 2. takerAddress |
+ // | | 0x040 | | 3. makerTokenAddress |
+ // | | 0x060 | | 4. takerTokenAddress |
+ // | | 0x080 | | 5. feeRecipientAddress |
+ // | | 0x0A0 | | 6. makerTokenAmount |
+ // | | 0x0C0 | | 7. takerTokenAmount |
+ // | | 0x0E0 | | 8. makerFeeAmount |
+ // | | 0x100 | | 9. takerFeeAmount |
+ // | | 0x120 | | 10. expirationTimeSeconds |
+ // | | 0x140 | | 11. salt |
+ // | | 0x160 | | 12. Offset to makerAssetProxyMetadata (*) |
+ // | | 0x180 | | 13. Offset to takerAssetProxyMetadata (* |
+ // | | 0x1A0 | 32 | makerAssetProxyMetadata Length |
+ // | | 0x1C0 | ** | makerAssetProxyMetadata Contents |
+ // | | 0x1E0 | 32 | takerAssetProxyMetadata Length |
+ // | | 0x200 | ** | takerAssetProxyMetadata Contents |
+ // | | 0x220 | 32 | signature Length |
+ // | | 0x240 | ** | signature Contents |
+
+ // * Offsets are calculated from the beginning of the current area: Header, Params, Data:
+ // An offset stored in the Params area is calculated from the beginning of the Params section.
+ // An offset stored in the Data area is calculated from the beginning of the Data section.
+
+ // ** The length of dynamic array contents are stored in the field immediately preceeding the contents.
+
// [1]: https://solidity.readthedocs.io/en/develop/abi-spec.html
bytes4 fillOrderSelector = this.fillOrder.selector;
assembly {
+
+ // Areas below may use the following variables:
+ // 1. <area>Start -- Start of this area in memory
+ // 2. <area>End -- End of this area in memory. This value may
+ // be precomputed (before writing contents),
+ // or it may be computed as contents are written.
+ // 3. <area>Offset -- Current offset into area. If an area's End
+ // is precomputed, this variable tracks the
+ // offsets of contents as they are written.
+
+ /////// Setup Header Area ///////
// Load free memory pointer
- let start := mload(0x40)
-
- // Write function signature
- mstore(start, fillOrderSelector)
-
- // Write order struct
- mstore(add(start, 4), mload(order)) // makerAddress
- mstore(add(start, 36), mload(add(order, 32))) // takerAddress
- mstore(add(start, 68), mload(add(order, 64))) // makerTokenAddress
- mstore(add(start, 100), mload(add(order, 96))) // takerTokenAddress
- mstore(add(start, 132), mload(add(order, 128))) // feeRecipientAddress
- mstore(add(start, 164), mload(add(order, 160))) // makerTokenAmount
- mstore(add(start, 196), mload(add(order, 192))) // takerTokenAmount
- mstore(add(start, 228), mload(add(order, 224))) // makerFeeAmount
- mstore(add(start, 260), mload(add(order, 256))) // takerFeeAmount
- mstore(add(start, 292), mload(add(order, 288))) // expirationTimeSeconds
- mstore(add(start, 324), mload(add(order, 320))) // salt
-
- // Write takerTokenFillAmount
- mstore(add(start, 356), takerTokenFillAmount)
-
- // Write signature offset
- mstore(add(start, 388), 416)
-
- // Write signature length
- let sigLen := mload(signature)
- mstore(add(start, 420), sigLen)
-
- // Calculate signature length with padding
- let paddingLen := mod(sub(0, sigLen), 32)
- let sigLenWithPadding := add(sigLen, paddingLen)
-
- // Write signature
- let sigStart := add(signature, 32)
- for { let curr := 0 }
- lt(curr, sigLenWithPadding)
- { curr := add(curr, 32) }
- { mstore(add(start, add(452, curr)), mload(add(sigStart, curr))) } // Note: we assume that padding consists of only 0's
+ let headerAreaStart := mload(0x40)
+ mstore(headerAreaStart, fillOrderSelector)
+ let headerAreaEnd := add(headerAreaStart, 0x4)
+
+ /////// Setup Params Area ///////
+ // This area is preallocated and written to later.
+ // This is because we need to fill in offsets that have not yet been calculated.
+ let paramsAreaStart := headerAreaEnd
+ let paramsAreaEnd := add(paramsAreaStart, 0x60)
+ let paramsAreaOffset := paramsAreaStart
+
+ /////// Setup Data Area ///////
+ let dataAreaStart := paramsAreaEnd
+ let dataAreaEnd := dataAreaStart
+
+ // Offset from the source data we're reading from
+ let sourceOffset := order
+ // bytesLen and bytesLenPadded track the length of a dynamically-allocated bytes array.
+ let bytesLen := 0
+ let bytesLenPadded := 0
+
+ /////// Write order Struct ///////
+ // Write memory location of Order, relative to the start of the
+ // parameter list, then increment the paramsAreaOffset respectively.
+ mstore(paramsAreaOffset, sub(dataAreaEnd, paramsAreaStart))
+ paramsAreaOffset := add(paramsAreaOffset, 0x20)
+
+ // Write values for each field in the order
+ for{let i := 0} lt(i, 13) {i := add(i, 1)} {
+ mstore(dataAreaEnd, mload(sourceOffset))
+ dataAreaEnd := add(dataAreaEnd, 0x20)
+ sourceOffset := add(sourceOffset, 0x20)
+ }
+
+ // Write offset to <order.makerAssetProxyMetadata>
+ mstore(add(dataAreaStart, mul(11, 0x20)), sub(dataAreaEnd, dataAreaStart))
+
+ // Calculate length of <order.makerAssetProxyMetadata>
+ bytesLen := mload(sourceOffset)
+ sourceOffset := add(sourceOffset, 0x20)
+ bytesLenPadded := add(div(bytesLen, 0x20), gt(mod(bytesLen, 0x20), 0))
+
+ // Write length of <order.makerAssetProxyMetadata>
+ mstore(dataAreaEnd, bytesLen)
+ dataAreaEnd := add(dataAreaEnd, 0x20)
+
+ // Write contents of <order.makerAssetProxyMetadata>
+ for {let i := 0} lt(i, bytesLenPadded) {i := add(i, 1)} {
+ mstore(dataAreaEnd, mload(sourceOffset))
+ dataAreaEnd := add(dataAreaEnd, 0x20)
+ sourceOffset := add(sourceOffset, 0x20)
+ }
+
+ // Write offset to <order.takerAssetProxyMetadata>
+ mstore(add(dataAreaStart, mul(12, 0x20)), sub(dataAreaEnd, dataAreaStart))
+
+ // Calculate length of <order.takerAssetProxyMetadata>
+ bytesLen := mload(sourceOffset)
+ sourceOffset := add(sourceOffset, 0x20)
+ bytesLenPadded := add(div(bytesLen, 0x20), gt(mod(bytesLen, 0x20), 0))
+
+ // Write length of <order.takerAssetProxyMetadata>
+ mstore(dataAreaEnd, bytesLen)
+ dataAreaEnd := add(dataAreaEnd, 0x20)
+
+ // Write contents of <order.takerAssetProxyMetadata>
+ for {let i := 0} lt(i, bytesLenPadded) {i := add(i, 1)} {
+ mstore(dataAreaEnd, mload(sourceOffset))
+ dataAreaEnd := add(dataAreaEnd, 0x20)
+ sourceOffset := add(sourceOffset, 0x20)
+ }
+
+ /////// Write takerTokenFillAmount ///////
+ mstore(paramsAreaOffset, takerTokenFillAmount)
+ paramsAreaOffset := add(paramsAreaOffset, 0x20)
+
+ /////// Write signature ///////
+ // Write offset to paramsArea
+ mstore(paramsAreaOffset, sub(dataAreaEnd, paramsAreaStart))
+
+ // Calculate length of signature
+ sourceOffset := signature
+ bytesLen := mload(sourceOffset)
+ sourceOffset := add(sourceOffset, 0x20)
+ bytesLenPadded := add(div(bytesLen, 0x20), gt(mod(bytesLen, 0x20), 0))
+
+ // Write length of signature
+ mstore(dataAreaEnd, bytesLen)
+ dataAreaEnd := add(dataAreaEnd, 0x20)
+
+ // Write contents of signature
+ for {let i := 0} lt(i, bytesLenPadded) {i := add(i, 1)} {
+ mstore(dataAreaEnd, mload(sourceOffset))
+ dataAreaEnd := add(dataAreaEnd, 0x20)
+ sourceOffset := add(sourceOffset, 0x20)
+ }
// Execute delegatecall
let success := delegatecall(
- gas, // forward all gas, TODO: look into gas consumption of assert/throw
- address, // call address of this contract
- start, // pointer to start of input
- add(452, sigLenWithPadding), // input length is 420 + signature length + padding length
- start, // write output over input
- 128 // output size is 128 bytes
+ gas, // forward all gas, TODO: look into gas consumption of assert/throw
+ address, // call address of this contract
+ headerAreaStart, // pointer to start of input
+ sub(dataAreaEnd, headerAreaStart), // length of input
+ headerAreaStart, // write output over input
+ 128 // output size is 128 bytes
)
switch success
case 0 {
@@ -144,12 +231,11 @@ contract MixinWrapperFunctions is
mstore(add(fillResults, 96), 0)
}
case 1 {
- mstore(fillResults, mload(start))
- mstore(add(fillResults, 32), mload(add(start, 32)))
- mstore(add(fillResults, 64), mload(add(start, 64)))
- mstore(add(fillResults, 96), mload(add(start, 96)))
+ mstore(fillResults, mload(headerAreaStart))
+ mstore(add(fillResults, 32), mload(add(headerAreaStart, 32)))
+ mstore(add(fillResults, 64), mload(add(headerAreaStart, 64)))
+ mstore(add(fillResults, 96), mload(add(headerAreaStart, 96)))
}
-
}
return fillResults;
}
@@ -228,10 +314,10 @@ contract MixinWrapperFunctions is
// Token being sold by taker must be the same for each order
require(orders[i].takerTokenAddress == orders[0].takerTokenAddress);
-
+
// Calculate the remaining amount of takerToken to sell
uint256 remainingTakerTokenFillAmount = safeSub(takerTokenFillAmount, totalFillResults.takerTokenFilledAmount);
-
+
// Attempt to sell the remaining amount of takerToken
FillResults memory singleFillResults = fillOrder(
orders[i],
@@ -270,7 +356,7 @@ contract MixinWrapperFunctions is
// Calculate the remaining amount of takerToken to sell
uint256 remainingTakerTokenFillAmount = safeSub(takerTokenFillAmount, totalFillResults.takerTokenFilledAmount);
-
+
// Attempt to sell the remaining amount of takerToken
FillResults memory singleFillResults = fillOrderNoThrow(
orders[i],
@@ -308,7 +394,7 @@ contract MixinWrapperFunctions is
// Calculate the remaining amount of makerToken to buy
uint256 remainingMakerTokenFillAmount = safeSub(makerTokenFillAmount, totalFillResults.makerTokenFilledAmount);
-
+
// Convert the remaining amount of makerToken to buy into remaining amount
// of takerToken to sell, assuming entire amount can be sold in the current order
uint256 remainingTakerTokenFillAmount = getPartialAmount(
@@ -405,5 +491,5 @@ contract MixinWrapperFunctions is
totalFillResults.makerFeePaid = safeAdd(totalFillResults.makerFeePaid, singleFillResults.makerFeePaid);
totalFillResults.takerFeePaid = safeAdd(totalFillResults.takerFeePaid, singleFillResults.takerFeePaid);
}
-
+
}
diff --git a/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol b/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol
new file mode 100644
index 000000000..61732a382
--- /dev/null
+++ b/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol
@@ -0,0 +1,46 @@
+/*
+
+ 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.21;
+import "/zeppelin/contracts/token/ERC721/ERC721Token.sol";
+import "../../utils/Ownable/Ownable.sol";
+
+contract DummyERC721Token is
+ Ownable,
+ ERC721Token
+{
+ function DummyERC721Token(
+ string name,
+ string symbol)
+ public
+ ERC721Token(name, symbol)
+ {}
+
+ /**
+ * @dev Internal function to mint a new token
+ * @dev Reverts if the given token ID already exists
+ * @param to address the beneficiary that will own the minted token
+ * @param tokenId uint256 ID of the token to be minted by the msg.sender
+ */
+ function mint(address to, uint256 tokenId)
+ public
+ onlyOwner
+ {
+ super._mint(to, tokenId);
+ }
+}
diff --git a/packages/contracts/src/contracts/current/utils/Authorizable/Authorizable.sol b/packages/contracts/src/contracts/current/utils/Authorizable/Authorizable.sol
new file mode 100644
index 000000000..cc27dd107
--- /dev/null
+++ b/packages/contracts/src/contracts/current/utils/Authorizable/Authorizable.sol
@@ -0,0 +1,109 @@
+/*
+
+ 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.21;
+
+import "./IAuthorizable.sol";
+import "../Ownable/Ownable.sol";
+
+contract Authorizable is
+ Ownable,
+ IAuthorizable
+{
+
+ /// @dev Only authorized addresses can invoke functions with this modifier.
+ modifier onlyAuthorized {
+ require(authorized[msg.sender]);
+ _;
+ }
+
+ modifier targetAuthorized(address target) {
+ require(authorized[target]);
+ _;
+ }
+
+ modifier targetNotAuthorized(address target) {
+ require(!authorized[target]);
+ _;
+ }
+
+ mapping (address => bool) public authorized;
+ address[] public authorities;
+
+ /*
+ * Public functions
+ */
+
+ /// @dev Authorizes an address.
+ /// @param target Address to authorize.
+ function addAuthorizedAddress(address target)
+ public
+ onlyOwner
+ targetNotAuthorized(target)
+ {
+ authorized[target] = true;
+ authorities.push(target);
+ emit LogAuthorizedAddressAdded(target, msg.sender);
+ }
+
+ /// @dev Removes authorizion of an address.
+ /// @param target Address to remove authorization from.
+ function removeAuthorizedAddress(address target)
+ public
+ onlyOwner
+ targetAuthorized(target)
+ {
+ delete authorized[target];
+ for (uint i = 0; i < authorities.length; i++) {
+ if (authorities[i] == target) {
+ authorities[i] = authorities[authorities.length - 1];
+ authorities.length -= 1;
+ break;
+ }
+ }
+ emit LogAuthorizedAddressRemoved(target, msg.sender);
+ }
+
+ /// @dev Removes authorizion of an address.
+ /// @param target Address to remove authorization from.
+ /// @param index Index of target in authorities array.
+ function removeAuthorizedAddressAtIndex(address target, uint256 index)
+ public
+ {
+ require(index < authorities.length);
+ require(authorities[index] == target);
+ delete authorized[target];
+ authorities[index] = authorities[authorities.length - 1];
+ authorities.length -= 1;
+ emit LogAuthorizedAddressRemoved(target, msg.sender);
+ }
+
+ /*
+ * Public constant functions
+ */
+
+ /// @dev Gets all authorized addresses.
+ /// @return Array of authorized addresses.
+ function getAuthorizedAddresses()
+ public
+ constant
+ returns (address[])
+ {
+ return authorities;
+ }
+}
diff --git a/packages/contracts/src/contracts/current/utils/Authorizable/IAuthorizable.sol b/packages/contracts/src/contracts/current/utils/Authorizable/IAuthorizable.sol
new file mode 100644
index 000000000..903fc1667
--- /dev/null
+++ b/packages/contracts/src/contracts/current/utils/Authorizable/IAuthorizable.sol
@@ -0,0 +1,53 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.21;
+
+/// @title TokenTransferProxy - Transfers tokens on behalf of contracts that have been approved via decentralized governance.
+contract IAuthorizable {
+
+ /// @dev Gets all authorized addresses.
+ /// @return Array of authorized addresses.
+ function getAuthorizedAddresses()
+ public view
+ returns (address[]);
+
+ /// @dev Authorizes an address.
+ /// @param target Address to authorize.
+ function addAuthorizedAddress(address target)
+ public;
+
+ /// @dev Removes authorizion of an address.
+ /// @param target Address to remove authorization from.
+ function removeAuthorizedAddress(address target)
+ public;
+
+ /// @dev Removes authorizion of an address.
+ /// @param target Address to remove authorization from.
+ /// @param index Index of target in authorities array.
+ function removeAuthorizedAddressAtIndex(address target, uint256 index)
+ public;
+
+ event LogAuthorizedAddressAdded(
+ address indexed target,
+ address indexed caller);
+
+ event LogAuthorizedAddressRemoved(
+ address indexed target,
+ address indexed caller);
+}
diff --git a/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol b/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol
new file mode 100644
index 000000000..32a51d8ab
--- /dev/null
+++ b/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol
@@ -0,0 +1,126 @@
+/*
+
+ 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.21;
+
+contract LibBytes {
+
+ /// @dev Reads an address from a position in a byte array.
+ /// @param b Byte array containing an address.
+ /// @param index Index in byte array of address.
+ /// @return address from byte array.
+ function readAddress(
+ bytes b,
+ uint256 index)
+ public pure
+ returns (address result)
+ {
+ require(b.length >= index + 20); // 20 is length of address
+
+ // Add offset to index:
+ // 1. Arrays are prefixed by 32-byte length parameter (add 32 to index)
+ // 2. Account for size difference between address length and 32-byte storage word (subtract 12 from index)
+ index += 20;
+
+ // Read address from array memory
+ assembly {
+ // 1. Add index to to address of bytes array
+ // 2. Load 32-byte word from memory
+ // 3. Apply 20-byte mask to obtain address
+ result := and(mload(add(b, index)), 0xffffffffffffffffffffffffffffffffffffffff)
+ }
+ return result;
+ }
+
+ /// @dev Writes an address into a specific position in a byte array.
+ /// @param input Address to put into byte array.
+ /// @param b Byte array to insert address into.
+ /// @param index Index in byte array of address.
+ function writeAddress(
+ address input,
+ bytes b,
+ uint256 index)
+ public pure
+ {
+ require(b.length >= index + 20); // 20 is length of address
+
+ // Add offset to index:
+ // 1. Arrays are prefixed by 32-byte length parameter (add 32 to index)
+ // 2. Account for size difference between address length and 32-byte storage word (subtract 12 from index)
+ index += 20;
+
+ // Store address into array memory
+ assembly {
+ // The address occupies 20 bytes and mstore stores 32 bytes.
+ // First fetch the 32-byte word where we'll be storing the address, then
+ // apply a mask so we have only the bytes in the word that the address will not occupy.
+ // Then combine these bytes with the address and store the 32 bytes back to memory with mstore.
+
+ // 1. Add index to address of bytes array
+ // 2. Load 32-byte word from memory
+ // 3. Apply 12-byte mask to obtain extra bytes occupying word of memory where we'll store the address
+ let neighbors := and(mload(add(b, index)), 0xffffffffffffffffffffffff0000000000000000000000000000000000000000)
+
+ // Store the neighbors and address into memory
+ mstore(add(b, index), xor(input, neighbors))
+ }
+ }
+
+ /// @dev Reads a uint256 value from a position in a byte array.
+ /// @param b Byte array containing a uint256 value.
+ /// @param index Index in byte array of uint256 value.
+ /// @return uint256 value from byte array.
+ function readUint256(
+ bytes b,
+ uint256 index)
+ public pure
+ returns (uint256 result)
+ {
+ require(b.length >= index + 32);
+
+ // Arrays are prefixed by a 256 bit length parameter
+ index += 32;
+
+ // Read the uint256 from array memory
+ assembly {
+ result := mload(add(b, index))
+ }
+ return result;
+ }
+
+ /// @dev Writes a uint256 into a specific position in a byte array.
+ /// @param input uint256 to put into byte array.
+ /// @param b Byte array to insert <input> into.
+ /// @param index Index in byte array of <input>.
+ function writeUint256(
+ uint256 input,
+ bytes b,
+ uint256 index)
+ public pure
+ {
+ require(b.length >= index + 32);
+
+ // Arrays are prefixed by a 256 bit length parameter
+ index += 32;
+
+ // Read the uint256 from array memory
+ assembly {
+ mstore(add(b, index), input)
+ }
+ }
+}
diff --git a/packages/contracts/src/contracts/current/utils/Ownable/IOwnable.sol b/packages/contracts/src/contracts/current/utils/Ownable/IOwnable.sol
new file mode 100644
index 000000000..7784a7ba9
--- /dev/null
+++ b/packages/contracts/src/contracts/current/utils/Ownable/IOwnable.sol
@@ -0,0 +1,13 @@
+pragma solidity ^0.4.21;
+
+/*
+ * Ownable
+ *
+ * Base contract with an owner.
+ * Provides onlyOwner modifier, which prevents function from running if it is called by anyone other than the owner.
+ */
+
+contract IOwnable {
+ function transferOwnership(address newOwner)
+ public;
+}
diff --git a/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol b/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol
index cb50f7252..91a5cb7ae 100644
--- a/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol
+++ b/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol
@@ -7,7 +7,9 @@ pragma solidity ^0.4.21;
* Provides onlyOwner modifier, which prevents function from running if it is called by anyone other than the owner.
*/
-contract Ownable {
+import "../Ownable/IOwnable.sol";
+
+contract Ownable is IOwnable {
address public owner;
function Ownable()
diff --git a/packages/contracts/src/utils/asset_proxy_utils.ts b/packages/contracts/src/utils/asset_proxy_utils.ts
new file mode 100644
index 000000000..938110a75
--- /dev/null
+++ b/packages/contracts/src/utils/asset_proxy_utils.ts
@@ -0,0 +1,67 @@
+import { BigNumber } from '@0xproject/utils';
+import * as Web3 from 'web3';
+
+import { AssetProxyId } from './types';
+const ethersUtils = require('ethers-utils');
+
+export function zeroPad(value: string, width: number): string {
+ return '0'.repeat(width - value.length) + value;
+}
+
+export function encodeAssetProxyId(assetProxyId: AssetProxyId, encoded_metadata: { value: string }) {
+ encoded_metadata.value += zeroPad(new BigNumber(assetProxyId).toString(16), 2);
+}
+
+export function encodeAddress(address: string, encoded_metadata: { value: string }) {
+ encoded_metadata.value += zeroPad(address.replace('0x', ''), 40);
+}
+
+export function encodeUint256(value: BigNumber, encoded_metadata: { value: string }) {
+ encoded_metadata.value += zeroPad(value.toString(16), 64);
+}
+
+export function encodeERC20ProxyMetadata_V1(tokenAddress: string) {
+ // Encode metadata
+ const encoded_metadata = { value: '0x' };
+ encodeAssetProxyId(AssetProxyId.ERC20_V1, encoded_metadata);
+ encodeAddress(tokenAddress, encoded_metadata);
+
+ // Verify encoding length - '0x' plus 21 bytes of encoded data
+ if (encoded_metadata.value.length != 44) {
+ throw Error('Bad encoding length. Expected 44, got ' + encoded_metadata.value.length);
+ }
+
+ // Return encoded metadata
+ return encoded_metadata.value;
+}
+
+export function encodeERC20ProxyMetadata(tokenAddress: string) {
+ // Encode metadata
+ const encoded_metadata = { value: '0x' };
+ encodeAssetProxyId(AssetProxyId.ERC20, encoded_metadata);
+ encodeAddress(tokenAddress, encoded_metadata);
+
+ // Verify encoding length - '0x' plus 21 bytes of encoded data
+ if (encoded_metadata.value.length != 44) {
+ throw Error('Bad encoding length. Expected 44, got ' + encoded_metadata.value.length);
+ }
+
+ // Return encoded metadata
+ return encoded_metadata.value;
+}
+
+export function encodeERC721ProxyMetadata(tokenAddress: string, tokenId: BigNumber) {
+ // Encode metadata
+ const encoded_metadata = { value: '0x' };
+ encodeAssetProxyId(AssetProxyId.ERC721, encoded_metadata);
+ encodeAddress(tokenAddress, encoded_metadata);
+ encodeUint256(tokenId, encoded_metadata);
+
+ // Verify encoding length - '0x' plus 53 bytes of encoded data
+ if (encoded_metadata.value.length != 108) {
+ throw Error('Bad encoding length. Expected 108, got ' + encoded_metadata.value.length);
+ }
+
+ // Return encoded metadata
+ return encoded_metadata.value;
+}
diff --git a/packages/contracts/src/utils/constants.ts b/packages/contracts/src/utils/constants.ts
index d31e1e285..49872fc59 100644
--- a/packages/contracts/src/utils/constants.ts
+++ b/packages/contracts/src/utils/constants.ts
@@ -26,5 +26,6 @@ export const constants = {
MAX_TOKEN_TRANSFERFROM_GAS: 80000,
MAX_TOKEN_APPROVE_GAS: 60000,
DUMMY_TOKEN_ARGS: [DUMMY_TOKEN_NAME, DUMMY_TOKEN_SYMBOL, DUMMY_TOKEN_DECIMALS, DUMMY_TOKEN_TOTAL_SUPPLY],
+ DUMMY_ERC721TOKEN_ARGS: [DUMMY_TOKEN_NAME, DUMMY_TOKEN_SYMBOL],
TESTRPC_PRIVATE_KEYS: _.map(TESTRPC_PRIVATE_KEYS_STRINGS, privateKeyString => ethUtil.toBuffer(privateKeyString)),
};
diff --git a/packages/contracts/src/utils/crypto.ts b/packages/contracts/src/utils/crypto.ts
index 810072d2f..5bc678cdf 100644
--- a/packages/contracts/src/utils/crypto.ts
+++ b/packages/contracts/src/utils/crypto.ts
@@ -31,6 +31,8 @@ export const crypto = {
argTypes.push('address');
} else if (_.isString(arg)) {
argTypes.push('string');
+ } else if (arg instanceof Buffer) {
+ argTypes.push('bytes');
} else if (_.isBoolean(arg)) {
argTypes.push('bool');
} else {
diff --git a/packages/contracts/src/utils/order_utils.ts b/packages/contracts/src/utils/order_utils.ts
index 26336c81d..8eb4da35a 100644
--- a/packages/contracts/src/utils/order_utils.ts
+++ b/packages/contracts/src/utils/order_utils.ts
@@ -35,6 +35,8 @@ export const orderUtils = {
takerFee: signedOrder.takerFee,
expirationTimeSeconds: signedOrder.expirationTimeSeconds,
salt: signedOrder.salt,
+ makerAssetProxyData: signedOrder.makerAssetProxyData,
+ takerAssetProxyData: signedOrder.takerAssetProxyData,
};
return orderStruct;
},
@@ -52,6 +54,8 @@ export const orderUtils = {
'uint256 takerFee',
'uint256 expirationTimeSeconds',
'uint256 salt',
+ 'bytes makerAssetProxyData',
+ 'bytes takerAssetProxyData',
]);
const orderParamsHashBuff = crypto.solSHA3([
order.exchangeAddress,
@@ -66,6 +70,8 @@ export const orderUtils = {
order.takerFee,
order.expirationTimeSeconds,
order.salt,
+ ethUtil.toBuffer(order.makerAssetProxyData),
+ ethUtil.toBuffer(order.takerAssetProxyData),
]);
const orderSchemaHashHex = `0x${orderSchemaHashBuff.toString('hex')}`;
const orderParamsHashHex = `0x${orderParamsHashBuff.toString('hex')}`;
diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts
index ed0ebeee9..f1636929b 100644
--- a/packages/contracts/src/utils/types.ts
+++ b/packages/contracts/src/utils/types.ts
@@ -37,6 +37,13 @@ export interface CancelOrdersBefore {
salt: BigNumber;
}
+export enum AssetProxyId {
+ INVALID,
+ ERC20_V1,
+ ERC20,
+ ERC721,
+}
+
export interface DefaultOrderParams {
exchangeAddress: string;
makerAddress: string;
@@ -45,8 +52,10 @@ export interface DefaultOrderParams {
takerTokenAddress: string;
makerTokenAmount: BigNumber;
takerTokenAmount: BigNumber;
- makerFee: BigNumber;
- takerFee: BigNumber;
+ makerFeeAmount: BigNumber;
+ takerFeeAmount: BigNumber;
+ makerAssetProxyData: string;
+ takerAssetProxyData: string;
}
export interface TransactionDataParams {
@@ -100,6 +109,11 @@ export enum ContractName {
AccountLevels = 'AccountLevels',
EtherDelta = 'EtherDelta',
Arbitrage = 'Arbitrage',
+ AssetProxyDispatcher = 'AssetProxyDispatcher',
+ ERC20TransferProxy = 'ERC20TransferProxy',
+ ERC20TransferProxy_V1 = 'ERC20TransferProxy_v1',
+ ERC721TransferProxy = 'ERC721TransferProxy',
+ DummyERC721Token = 'DummyERC721Token',
}
export interface Artifact {
@@ -134,6 +148,8 @@ export interface OrderStruct {
takerFee: BigNumber;
expirationTimeSeconds: BigNumber;
salt: BigNumber;
+ makerAssetProxyData: string;
+ takerAssetProxyData: string;
}
export interface UnsignedOrder extends OrderStruct {
diff --git a/packages/contracts/test/asset_proxy_dispatcher/dispatcher.ts b/packages/contracts/test/asset_proxy_dispatcher/dispatcher.ts
new file mode 100644
index 000000000..6cb9f2bc1
--- /dev/null
+++ b/packages/contracts/test/asset_proxy_dispatcher/dispatcher.ts
@@ -0,0 +1,339 @@
+import { LogWithDecodedArgs, TransactionReceiptWithDecodedLogs, ZeroEx } from '0x.js';
+import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
+import { BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import * as chai from 'chai';
+import * as Web3 from 'web3';
+
+import { AssetProxyDispatcherContract } from '../../src/contract_wrappers/generated/asset_proxy_dispatcher';
+import { DummyERC721TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c721_token';
+import { DummyTokenContract } from '../../src/contract_wrappers/generated/dummy_token';
+import { ERC20TransferProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_transfer_proxy';
+import { ERC721TransferProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_transfer_proxy';
+import { ERC20TransferProxy_v1Contract } from '../../src/contract_wrappers/generated/erc20transferproxy_v1';
+import { TokenTransferProxyContract } from '../../src/contract_wrappers/generated/token_transfer_proxy';
+import {
+ encodeERC20ProxyMetadata,
+ encodeERC20ProxyMetadata_V1,
+ encodeERC721ProxyMetadata,
+} from '../../src/utils/asset_proxy_utils';
+import { Balances } from '../../src/utils/balances';
+import { constants } from '../../src/utils/constants';
+import { AssetProxyId, ContractName } from '../../src/utils/types';
+import { chaiSetup } from '../utils/chai_setup';
+import { deployer } from '../utils/deployer';
+import { provider, web3Wrapper } from '../utils/web3_wrapper';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+describe('AssetProxyDispatcher', () => {
+ let owner: string;
+ let notOwner: string;
+ let assetProxyManagerAddress: string;
+ let tokenOwner: string;
+ let makerAddress: string;
+ let takerAddress: string;
+ let zrx: DummyTokenContract;
+ let dmyBalances: Balances;
+ let tokenTransferProxy: TokenTransferProxyContract;
+ let assetProxyDispatcher: AssetProxyDispatcherContract;
+ let erc20TransferProxyV1: ERC20TransferProxy_v1Contract;
+ let erc20TransferProxy: ERC20TransferProxyContract;
+ let erc721TransferProxy: ERC721TransferProxyContract;
+ const nilAddress = '0x0000000000000000000000000000000000000000';
+ const INITIAL_BALANCE = new BigNumber(10000);
+
+ before(async () => {
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ owner = tokenOwner = accounts[0];
+ notOwner = accounts[1];
+ assetProxyManagerAddress = accounts[2];
+ makerAddress = accounts[3];
+ takerAddress = accounts[4];
+ const tokenTransferProxyInstance = await deployer.deployAsync(ContractName.TokenTransferProxy);
+ tokenTransferProxy = new TokenTransferProxyContract(
+ tokenTransferProxyInstance.abi,
+ tokenTransferProxyInstance.address,
+ provider,
+ );
+
+ const erc20TransferProxyV1Instance = await deployer.deployAsync(ContractName.ERC20TransferProxy_V1, [
+ tokenTransferProxy.address,
+ ]);
+ erc20TransferProxyV1 = new ERC20TransferProxy_v1Contract(
+ erc20TransferProxyV1Instance.abi,
+ erc20TransferProxyV1Instance.address,
+ provider,
+ );
+
+ const erc20TransferProxyInstance = await deployer.deployAsync(ContractName.ERC20TransferProxy);
+ erc20TransferProxy = new ERC20TransferProxyContract(
+ erc20TransferProxyInstance.abi,
+ erc20TransferProxyInstance.address,
+ provider,
+ );
+
+ const erc721TransferProxyInstance = await deployer.deployAsync(ContractName.ERC721TransferProxy);
+ erc721TransferProxy = new ERC721TransferProxyContract(
+ erc721TransferProxyInstance.abi,
+ erc721TransferProxyInstance.address,
+ provider,
+ );
+
+ const assetProxyDispatcherInstance = await deployer.deployAsync(ContractName.AssetProxyDispatcher);
+ assetProxyDispatcher = new AssetProxyDispatcherContract(
+ assetProxyDispatcherInstance.abi,
+ assetProxyDispatcherInstance.address,
+ provider,
+ );
+
+ const zrxInstance = await deployer.deployAsync(ContractName.DummyToken, constants.DUMMY_TOKEN_ARGS);
+ zrx = new DummyTokenContract(zrxInstance.abi, zrxInstance.address, provider);
+ await zrx.setBalance.sendTransactionAsync(makerAddress, INITIAL_BALANCE, { from: tokenOwner });
+ await zrx.setBalance.sendTransactionAsync(takerAddress, INITIAL_BALANCE, { from: tokenOwner });
+ dmyBalances = new Balances([zrx], [makerAddress, takerAddress]);
+ await zrx.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_BALANCE, {
+ from: takerAddress,
+ });
+ await zrx.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_BALANCE, {
+ from: makerAddress,
+ });
+
+ await assetProxyDispatcher.addAuthorizedAddress.sendTransactionAsync(assetProxyManagerAddress, {
+ from: accounts[0],
+ });
+ await erc20TransferProxyV1.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, {
+ from: accounts[0],
+ });
+ await erc20TransferProxy.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, {
+ from: accounts[0],
+ });
+ await erc721TransferProxy.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, {
+ from: accounts[0],
+ });
+ await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(erc20TransferProxyV1.address, {
+ from: accounts[0],
+ });
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+
+ describe('setAssetProxy', () => {
+ it('should record proxy upon registration', async () => {
+ await assetProxyDispatcher.setAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20TransferProxy.address,
+ nilAddress,
+ { from: owner },
+ );
+ const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20);
+ expect(proxyAddress).to.be.equal(erc20TransferProxy.address);
+ });
+
+ it('should be able to record multiple proxies', async () => {
+ await assetProxyDispatcher.setAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20TransferProxy.address,
+ nilAddress,
+ { from: owner },
+ );
+ let proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20);
+ expect(proxyAddress).to.be.equal(erc20TransferProxy.address);
+
+ await assetProxyDispatcher.setAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC721,
+ erc721TransferProxy.address,
+ nilAddress,
+ { from: owner },
+ );
+ proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC721);
+ expect(proxyAddress).to.be.equal(erc721TransferProxy.address);
+ });
+
+ it('should replace proxy address upon re-registration', async () => {
+ await assetProxyDispatcher.setAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20TransferProxy.address,
+ nilAddress,
+ { from: owner },
+ );
+ let proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20);
+ expect(proxyAddress).to.be.equal(erc20TransferProxy.address);
+
+ // Deploy a new version of the ERC20 Transfer Proxy contract
+ const newErc20TransferProxyInstance = await deployer.deployAsync(ContractName.ERC20TransferProxy);
+ const newErc20TransferProxy = new ERC20TransferProxyContract(
+ newErc20TransferProxyInstance.abi,
+ newErc20TransferProxyInstance.address,
+ provider,
+ );
+
+ const newAddress = newErc20TransferProxy.address;
+ const currentAddress = erc20TransferProxy.address;
+ await assetProxyDispatcher.setAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ newAddress,
+ currentAddress,
+ { from: owner },
+ );
+ proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20);
+ expect(proxyAddress).to.be.equal(newAddress);
+ });
+
+ it('should throw if registering with incorrect "old_address" field', async () => {
+ await assetProxyDispatcher.setAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20TransferProxy.address,
+ nilAddress,
+ { from: owner },
+ );
+ const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20);
+ expect(proxyAddress).to.be.equal(erc20TransferProxy.address);
+
+ // The following transaction will throw because the currentAddress is no longer nilAddress
+ return expect(
+ assetProxyDispatcher.setAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20TransferProxy.address,
+ nilAddress,
+ { from: owner },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should be able to reset proxy address to NULL', async () => {
+ await assetProxyDispatcher.setAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20TransferProxy.address,
+ nilAddress,
+ { from: owner },
+ );
+ const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20);
+ expect(proxyAddress).to.be.equal(erc20TransferProxy.address);
+
+ // The following transaction will reset the proxy address
+ await assetProxyDispatcher.setAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ nilAddress,
+ erc20TransferProxy.address,
+ { from: owner },
+ );
+ const newProxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20);
+ expect(newProxyAddress).to.be.equal(nilAddress);
+ });
+
+ it('should throw if requesting address is not authorized', async () => {
+ return expect(
+ assetProxyDispatcher.setAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20TransferProxy.address,
+ nilAddress,
+ { from: notOwner },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+ });
+
+ describe('getAssetProxy', () => {
+ it('should return correct address of registered proxy', async () => {
+ await assetProxyDispatcher.setAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20TransferProxy.address,
+ nilAddress,
+ { from: owner },
+ );
+ const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20);
+ expect(proxyAddress).to.be.equal(erc20TransferProxy.address);
+ });
+
+ it('should return NULL address if requesting non-existent proxy', async () => {
+ const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20);
+ expect(proxyAddress).to.be.equal(nilAddress);
+ });
+ });
+
+ describe('transferFrom', () => {
+ it('should dispatch transfer to registered proxy', async () => {
+ // Register ERC20 proxy
+ await assetProxyDispatcher.setAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20TransferProxy.address,
+ nilAddress,
+ { from: owner },
+ );
+
+ // Construct metadata for ERC20 proxy
+ const encodedProxyMetadata = encodeERC20ProxyMetadata(zrx.address);
+
+ // Perform a transfer from makerAddress to takerAddress
+ const balances = await dmyBalances.getAsync();
+ const amount = new BigNumber(10);
+ await assetProxyDispatcher.transferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ { from: assetProxyManagerAddress },
+ );
+
+ // Verify transfer was successful
+ const newBalances = await dmyBalances.getAsync();
+ expect(newBalances[makerAddress][zrx.address]).to.be.bignumber.equal(
+ balances[makerAddress][zrx.address].minus(amount),
+ );
+ expect(newBalances[takerAddress][zrx.address]).to.be.bignumber.equal(
+ balances[takerAddress][zrx.address].add(amount),
+ );
+ });
+
+ it('should throw if delegating to unregistered proxy', async () => {
+ // Construct metadata for ERC20 proxy
+ const encodedProxyMetadata = encodeERC20ProxyMetadata(zrx.address);
+
+ // Perform a transfer from makerAddress to takerAddress
+ const balances = await dmyBalances.getAsync();
+ const amount = new BigNumber(10);
+ return expect(
+ assetProxyDispatcher.transferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ { from: notOwner },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should throw if requesting address is not authorized', async () => {
+ // Register ERC20 proxy
+ await assetProxyDispatcher.setAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20TransferProxy.address,
+ nilAddress,
+ { from: owner },
+ );
+
+ // Construct metadata for ERC20 proxy
+ const encodedProxyMetadata = encodeERC20ProxyMetadata(zrx.address);
+
+ // Perform a transfer from makerAddress to takerAddress
+ const balances = await dmyBalances.getAsync();
+ const amount = new BigNumber(10);
+ return expect(
+ assetProxyDispatcher.transferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ { from: notOwner },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+ });
+});
diff --git a/packages/contracts/test/asset_proxy_dispatcher/proxies.ts b/packages/contracts/test/asset_proxy_dispatcher/proxies.ts
new file mode 100644
index 000000000..da1bb170c
--- /dev/null
+++ b/packages/contracts/test/asset_proxy_dispatcher/proxies.ts
@@ -0,0 +1,407 @@
+import { LogWithDecodedArgs, TransactionReceiptWithDecodedLogs, ZeroEx } from '0x.js';
+import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
+import { BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import * as chai from 'chai';
+import * as Web3 from 'web3';
+
+import { AssetProxyDispatcherContract } from '../../src/contract_wrappers/generated/asset_proxy_dispatcher';
+import { DummyERC721TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c721_token';
+import { DummyTokenContract } from '../../src/contract_wrappers/generated/dummy_token';
+import { ERC20TransferProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_transfer_proxy';
+import { ERC721TransferProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_transfer_proxy';
+import { ERC20TransferProxy_v1Contract } from '../../src/contract_wrappers/generated/erc20transferproxy_v1';
+import { TokenTransferProxyContract } from '../../src/contract_wrappers/generated/token_transfer_proxy';
+import {
+ encodeERC20ProxyMetadata,
+ encodeERC20ProxyMetadata_V1,
+ encodeERC721ProxyMetadata,
+} from '../../src/utils/asset_proxy_utils';
+import { Balances } from '../../src/utils/balances';
+import { constants } from '../../src/utils/constants';
+import { AssetProxyId, ContractName } from '../../src/utils/types';
+import { chaiSetup } from '../utils/chai_setup';
+import { deployer } from '../utils/deployer';
+import { provider, web3Wrapper } from '../utils/web3_wrapper';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+describe('Asset Transfer Proxies', () => {
+ let owner: string;
+ let notOwner: string;
+ let assetProxyManagerAddress: string;
+ let tokenOwner: string;
+ let makerAddress: string;
+ let takerAddress: string;
+ let zrx: DummyTokenContract;
+ let ck: DummyERC721TokenContract;
+ let dmyBalances: Balances;
+ let tokenTransferProxy: TokenTransferProxyContract;
+ let assetProxyDispatcher: AssetProxyDispatcherContract;
+ let erc20TransferProxyV1: ERC20TransferProxy_v1Contract;
+ let erc20TransferProxy: ERC20TransferProxyContract;
+ let erc721TransferProxy: ERC721TransferProxyContract;
+ const nilAddress = '0x0000000000000000000000000000000000000000';
+ const makerTokenId = new BigNumber('0x1010101010101010101010101010101010101010101010101010101010101010');
+ const INITIAL_BALANCE = new BigNumber(10000);
+
+ before(async () => {
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ owner = tokenOwner = accounts[0];
+ notOwner = accounts[1];
+ assetProxyManagerAddress = accounts[2];
+ makerAddress = accounts[3];
+ takerAddress = accounts[4];
+ const tokenTransferProxyInstance = await deployer.deployAsync(ContractName.TokenTransferProxy);
+ tokenTransferProxy = new TokenTransferProxyContract(
+ tokenTransferProxyInstance.abi,
+ tokenTransferProxyInstance.address,
+ provider,
+ );
+
+ const erc20TransferProxyV1Instance = await deployer.deployAsync(ContractName.ERC20TransferProxy_V1, [
+ tokenTransferProxy.address,
+ ]);
+ erc20TransferProxyV1 = new ERC20TransferProxy_v1Contract(
+ erc20TransferProxyV1Instance.abi,
+ erc20TransferProxyV1Instance.address,
+ provider,
+ );
+
+ const erc20TransferProxyInstance = await deployer.deployAsync(ContractName.ERC20TransferProxy);
+ erc20TransferProxy = new ERC20TransferProxyContract(
+ erc20TransferProxyInstance.abi,
+ erc20TransferProxyInstance.address,
+ provider,
+ );
+
+ const erc721TransferProxyInstance = await deployer.deployAsync(ContractName.ERC721TransferProxy);
+ erc721TransferProxy = new ERC721TransferProxyContract(
+ erc721TransferProxyInstance.abi,
+ erc721TransferProxyInstance.address,
+ provider,
+ );
+
+ const assetProxyDispatcherInstance = await deployer.deployAsync(ContractName.AssetProxyDispatcher);
+ assetProxyDispatcher = new AssetProxyDispatcherContract(
+ assetProxyDispatcherInstance.abi,
+ assetProxyDispatcherInstance.address,
+ provider,
+ );
+
+ const zrxInstance = await deployer.deployAsync(ContractName.DummyToken, constants.DUMMY_TOKEN_ARGS);
+ zrx = new DummyTokenContract(zrxInstance.abi, zrxInstance.address, provider);
+ await zrx.setBalance.sendTransactionAsync(makerAddress, INITIAL_BALANCE, { from: tokenOwner });
+ await zrx.setBalance.sendTransactionAsync(takerAddress, INITIAL_BALANCE, { from: tokenOwner });
+ dmyBalances = new Balances([zrx], [makerAddress, takerAddress]);
+ await zrx.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_BALANCE, {
+ from: takerAddress,
+ });
+ await zrx.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_BALANCE, {
+ from: makerAddress,
+ });
+ await zrx.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_BALANCE, {
+ from: takerAddress,
+ });
+ await zrx.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_BALANCE, {
+ from: makerAddress,
+ });
+
+ const ckInstance = await deployer.deployAsync(ContractName.DummyERC721Token, constants.DUMMY_ERC721TOKEN_ARGS);
+ ck = new DummyERC721TokenContract(ckInstance.abi, ckInstance.address, provider);
+ await ck.setApprovalForAll.sendTransactionAsync(erc721TransferProxy.address, true, { from: makerAddress });
+ await ck.setApprovalForAll.sendTransactionAsync(erc721TransferProxy.address, true, { from: takerAddress });
+ await ck.mint.sendTransactionAsync(makerAddress, makerTokenId, { from: tokenOwner });
+ await assetProxyDispatcher.addAuthorizedAddress.sendTransactionAsync(assetProxyManagerAddress, {
+ from: accounts[0],
+ });
+ await erc20TransferProxyV1.addAuthorizedAddress.sendTransactionAsync(assetProxyManagerAddress, {
+ from: accounts[0],
+ });
+ await erc20TransferProxy.addAuthorizedAddress.sendTransactionAsync(assetProxyManagerAddress, {
+ from: accounts[0],
+ });
+ await erc721TransferProxy.addAuthorizedAddress.sendTransactionAsync(assetProxyManagerAddress, {
+ from: accounts[0],
+ });
+ await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(erc20TransferProxyV1.address, {
+ from: accounts[0],
+ });
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+
+ describe('Transfer Proxy - ERC20_V1', () => {
+ it('should successfully encode/decode metadata', async () => {
+ const metadata = await erc20TransferProxyV1.encodeMetadata.callAsync(AssetProxyId.ERC20_V1, zrx.address);
+ const address = await erc20TransferProxyV1.decodeMetadata.callAsync(metadata);
+ expect(address).to.be.equal(zrx.address);
+ });
+
+ it('should successfully decode metadata encoded by typescript helpers', async () => {
+ const metadata = encodeERC20ProxyMetadata_V1(zrx.address);
+ const address = await erc20TransferProxyV1.decodeMetadata.callAsync(metadata);
+ expect(address).to.be.equal(zrx.address);
+ });
+
+ it('should successfully encode/decode metadata padded with zeros', async () => {
+ const testAddress = '0x0000000000000000056000000000000000000010';
+ const metadata = await erc20TransferProxyV1.encodeMetadata.callAsync(AssetProxyId.ERC20_V1, testAddress);
+ const address = await erc20TransferProxyV1.decodeMetadata.callAsync(metadata);
+ expect(address).to.be.equal(testAddress);
+ });
+
+ it('should successfully decode metadata encoded padded with zeros by typescript helpers', async () => {
+ const testAddress = '0x0000000000000000056000000000000000000010';
+ const metadata = encodeERC20ProxyMetadata_V1(testAddress);
+ const address = await erc20TransferProxyV1.decodeMetadata.callAsync(metadata);
+ expect(address).to.be.equal(testAddress);
+ });
+
+ it('should successfully transfer tokens', async () => {
+ // Construct metadata for ERC20 proxy
+ const encodedProxyMetadata = encodeERC20ProxyMetadata_V1(zrx.address);
+
+ // Perform a transfer from makerAddress to takerAddress
+ const balances = await dmyBalances.getAsync();
+ const amount = new BigNumber(10);
+ await erc20TransferProxyV1.transferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ { from: assetProxyManagerAddress },
+ );
+
+ // Verify transfer was successful
+ const newBalances = await dmyBalances.getAsync();
+ expect(newBalances[makerAddress][zrx.address]).to.be.bignumber.equal(
+ balances[makerAddress][zrx.address].minus(amount),
+ );
+ expect(newBalances[takerAddress][zrx.address]).to.be.bignumber.equal(
+ balances[takerAddress][zrx.address].add(amount),
+ );
+ });
+
+ it('should throw if requesting address is not authorized', async () => {
+ // Construct metadata for ERC20 proxy
+ const encodedProxyMetadata = encodeERC20ProxyMetadata_V1(zrx.address);
+
+ // Perform a transfer from makerAddress to takerAddress
+ const balances = await dmyBalances.getAsync();
+ const amount = new BigNumber(10);
+ expect(
+ erc20TransferProxyV1.transferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ { from: notOwner },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+ });
+
+ describe('Transfer Proxy - ERC20', () => {
+ it('should successfully encode/decode metadata', async () => {
+ const metadata = await erc20TransferProxy.encodeMetadata.callAsync(AssetProxyId.ERC20, zrx.address);
+ const address = await erc20TransferProxy.decodeMetadata.callAsync(metadata);
+ expect(address).to.be.equal(zrx.address);
+ });
+
+ it('should successfully decode metadata encoded by typescript helpers', async () => {
+ const metadata = encodeERC20ProxyMetadata(zrx.address);
+ const address = await erc20TransferProxy.decodeMetadata.callAsync(metadata);
+ expect(address).to.be.equal(zrx.address);
+ });
+
+ it('should successfully encode/decode metadata padded with zeros', async () => {
+ const testAddress = '0x0000000000000000056000000000000000000010';
+ const metadata = await erc20TransferProxy.encodeMetadata.callAsync(AssetProxyId.ERC20, testAddress);
+ const address = await erc20TransferProxy.decodeMetadata.callAsync(metadata);
+ expect(address).to.be.equal(testAddress);
+ });
+
+ it('should successfully decode metadata encoded padded with zeros by typescript helpers', async () => {
+ const testAddress = '0x0000000000000000056000000000000000000010';
+ const metadata = encodeERC20ProxyMetadata(testAddress);
+ const address = await erc20TransferProxy.decodeMetadata.callAsync(metadata);
+ expect(address).to.be.equal(testAddress);
+ });
+
+ it('should successfully transfer tokens', async () => {
+ // Construct metadata for ERC20 proxy
+ const encodedProxyMetadata = encodeERC20ProxyMetadata(zrx.address);
+
+ // Perform a transfer from makerAddress to takerAddress
+ const balances = await dmyBalances.getAsync();
+ const amount = new BigNumber(10);
+ await erc20TransferProxy.transferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ { from: assetProxyManagerAddress },
+ );
+
+ // Verify transfer was successful
+ const newBalances = await dmyBalances.getAsync();
+ expect(newBalances[makerAddress][zrx.address]).to.be.bignumber.equal(
+ balances[makerAddress][zrx.address].minus(amount),
+ );
+ expect(newBalances[takerAddress][zrx.address]).to.be.bignumber.equal(
+ balances[takerAddress][zrx.address].add(amount),
+ );
+ });
+
+ it('should throw if requesting address is not authorized', async () => {
+ // Construct metadata for ERC20 proxy
+ const encodedProxyMetadata = encodeERC20ProxyMetadata(zrx.address);
+
+ // Perform a transfer from makerAddress to takerAddress
+ const balances = await dmyBalances.getAsync();
+ const amount = new BigNumber(10);
+ expect(
+ erc20TransferProxy.transferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ { from: notOwner },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+ });
+
+ describe('Transfer Proxy - ERC721', () => {
+ it('should successfully encode/decode metadata', async () => {
+ const metadata = await erc721TransferProxy.encodeMetadata.callAsync(
+ AssetProxyId.ERC721,
+ ck.address,
+ makerTokenId,
+ );
+ const [address, tokenId] = await erc721TransferProxy.decodeMetadata.callAsync(metadata);
+ expect(address).to.be.equal(ck.address);
+ expect(tokenId).to.be.bignumber.equal(makerTokenId);
+ });
+
+ it('should successfully decode metadata encoded by typescript helpers', async () => {
+ const metadata = encodeERC721ProxyMetadata(ck.address, makerTokenId);
+ const [address, tokenId] = await erc721TransferProxy.decodeMetadata.callAsync(metadata);
+ expect(address).to.be.equal(ck.address);
+ expect(tokenId).to.be.bignumber.equal(makerTokenId);
+ });
+
+ it('should successfully encode/decode metadata padded with zeros', async () => {
+ const testAddress = '0x0000000000000000056000000000000000000010';
+ const metadata = await erc721TransferProxy.encodeMetadata.callAsync(
+ AssetProxyId.ERC721,
+ testAddress,
+ makerTokenId,
+ );
+ const [address, tokenId] = await erc721TransferProxy.decodeMetadata.callAsync(metadata);
+ expect(address).to.be.equal(testAddress);
+ expect(tokenId).to.be.bignumber.equal(makerTokenId);
+ });
+
+ it('should successfully decode metadata encoded padded with zeros by typescript helpers', async () => {
+ const testAddress = '0x0000000000000000056000000000000000000010';
+ const metadata = encodeERC721ProxyMetadata(testAddress, makerTokenId);
+ const [address, tokenId] = await erc721TransferProxy.decodeMetadata.callAsync(metadata);
+ expect(address).to.be.equal(testAddress);
+ expect(tokenId).to.be.bignumber.equal(makerTokenId);
+ });
+
+ it('should successfully transfer tokens', async () => {
+ // Construct metadata for ERC20 proxy
+ const encodedProxyMetadata = encodeERC721ProxyMetadata(ck.address, makerTokenId);
+
+ // Verify pre-condition
+ const ownerMakerToken = await ck.ownerOf.callAsync(makerTokenId);
+ expect(ownerMakerToken).to.be.bignumber.equal(makerAddress);
+
+ // Perform a transfer from makerAddress to takerAddress
+ const balances = await dmyBalances.getAsync();
+ const amount = new BigNumber(1);
+ await erc721TransferProxy.transferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ { from: assetProxyManagerAddress },
+ );
+
+ // Verify transfer was successful
+ const newOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId);
+ expect(newOwnerMakerToken).to.be.bignumber.equal(takerAddress);
+ });
+
+ it('should throw if transferring 0 amount of a token', async () => {
+ // Construct metadata for ERC20 proxy
+ const encodedProxyMetadata = encodeERC721ProxyMetadata(ck.address, makerTokenId);
+
+ // Verify pre-condition
+ const ownerMakerToken = await ck.ownerOf.callAsync(makerTokenId);
+ expect(ownerMakerToken).to.be.bignumber.equal(makerAddress);
+
+ // Perform a transfer from makerAddress to takerAddress
+ const balances = await dmyBalances.getAsync();
+ const amount = new BigNumber(0);
+ expect(
+ erc20TransferProxy.transferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ { from: notOwner },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should throw if transferring >1 amount of a token', async () => {
+ // Construct metadata for ERC20 proxy
+ const encodedProxyMetadata = encodeERC721ProxyMetadata(ck.address, makerTokenId);
+
+ // Verify pre-condition
+ const ownerMakerToken = await ck.ownerOf.callAsync(makerTokenId);
+ expect(ownerMakerToken).to.be.bignumber.equal(makerAddress);
+
+ // Perform a transfer from makerAddress to takerAddress
+ const balances = await dmyBalances.getAsync();
+ const amount = new BigNumber(500);
+ expect(
+ erc20TransferProxy.transferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ { from: notOwner },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should throw if requesting address is not authorized', async () => {
+ // Construct metadata for ERC20 proxy
+ const encodedProxyMetadata = encodeERC721ProxyMetadata(zrx.address, makerTokenId);
+
+ // Perform a transfer from makerAddress to takerAddress
+ const balances = await dmyBalances.getAsync();
+ const amount = new BigNumber(1);
+ expect(
+ erc20TransferProxy.transferFrom.sendTransactionAsync(
+ encodedProxyMetadata,
+ makerAddress,
+ takerAddress,
+ amount,
+ { from: notOwner },
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+ });
+});
diff --git a/packages/contracts/test/exchange/core.ts b/packages/contracts/test/exchange/core.ts
index ef3b3b9ee..1277a88f3 100644
--- a/packages/contracts/test/exchange/core.ts
+++ b/packages/contracts/test/exchange/core.ts
@@ -1,13 +1,18 @@
import { LogWithDecodedArgs, TransactionReceiptWithDecodedLogs, ZeroEx } from '0x.js';
-
import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
import { BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as chai from 'chai';
import ethUtil = require('ethereumjs-util');
+import * as _ from 'lodash';
import * as Web3 from 'web3';
+import { AssetProxyDispatcherContract } from '../../src/contract_wrappers/generated/asset_proxy_dispatcher';
+import { DummyERC721TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c721_token';
import { DummyTokenContract } from '../../src/contract_wrappers/generated/dummy_token';
+import { ERC20TransferProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_transfer_proxy';
+import { ERC721TransferProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_transfer_proxy';
+import { ERC20TransferProxy_v1Contract } from '../../src/contract_wrappers/generated/erc20transferproxy_v1';
import {
CancelContractEventArgs,
ExchangeContract,
@@ -15,13 +20,25 @@ import {
FillContractEventArgs,
} from '../../src/contract_wrappers/generated/exchange';
import { TokenTransferProxyContract } from '../../src/contract_wrappers/generated/token_transfer_proxy';
+import {
+ encodeERC20ProxyMetadata,
+ encodeERC20ProxyMetadata_V1,
+ encodeERC721ProxyMetadata,
+} from '../../src/utils/asset_proxy_utils';
import { Balances } from '../../src/utils/balances';
import { constants } from '../../src/utils/constants';
import { crypto } from '../../src/utils/crypto';
import { ExchangeWrapper } from '../../src/utils/exchange_wrapper';
import { OrderFactory } from '../../src/utils/order_factory';
import { orderUtils } from '../../src/utils/order_utils';
-import { BalancesByOwner, ContractName, ExchangeContractErrs, SignatureType, SignedOrder } from '../../src/utils/types';
+import {
+ AssetProxyId,
+ BalancesByOwner,
+ ContractName,
+ ExchangeContractErrs,
+ SignatureType,
+ SignedOrder,
+} from '../../src/utils/types';
import { chaiSetup } from '../utils/chai_setup';
import { deployer } from '../utils/deployer';
import { provider, web3Wrapper } from '../utils/web3_wrapper';
@@ -35,14 +52,21 @@ describe('Exchange', () => {
let tokenOwner: string;
let takerAddress: string;
let feeRecipientAddress: string;
+ let assetProxyManagerAddress: string;
const INITIAL_BALANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18);
const INITIAL_ALLOWANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18);
let rep: DummyTokenContract;
let dgd: DummyTokenContract;
let zrx: DummyTokenContract;
+ let ck: DummyERC721TokenContract;
+ let et: DummyERC721TokenContract;
let exchange: ExchangeContract;
let tokenTransferProxy: TokenTransferProxyContract;
+ let assetProxyDispatcher: AssetProxyDispatcherContract;
+ let erc20TransferProxyV1: ERC20TransferProxy_v1Contract;
+ let erc20TransferProxy: ERC20TransferProxyContract;
+ let erc721TransferProxy: ERC721TransferProxyContract;
let signedOrder: SignedOrder;
let balances: BalancesByOwner;
@@ -50,32 +74,104 @@ describe('Exchange', () => {
let dmyBalances: Balances;
let orderFactory: OrderFactory;
+ let erc721TransferProxyInstance: Web3.ContractInstance;
+
let zeroEx: ZeroEx;
before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
makerAddress = accounts[0];
- [tokenOwner, takerAddress, feeRecipientAddress] = accounts;
- const [repInstance, dgdInstance, zrxInstance] = await Promise.all([
+ [tokenOwner, takerAddress, feeRecipientAddress, assetProxyManagerAddress] = accounts;
+ const [repInstance, dgdInstance, zrxInstance, ckInstance, etInstance] = await Promise.all([
deployer.deployAsync(ContractName.DummyToken, constants.DUMMY_TOKEN_ARGS),
deployer.deployAsync(ContractName.DummyToken, constants.DUMMY_TOKEN_ARGS),
deployer.deployAsync(ContractName.DummyToken, constants.DUMMY_TOKEN_ARGS),
+ deployer.deployAsync(ContractName.DummyERC721Token, constants.DUMMY_ERC721TOKEN_ARGS),
+ deployer.deployAsync(ContractName.DummyERC721Token, constants.DUMMY_ERC721TOKEN_ARGS),
]);
rep = new DummyTokenContract(repInstance.abi, repInstance.address, provider);
dgd = new DummyTokenContract(dgdInstance.abi, dgdInstance.address, provider);
zrx = new DummyTokenContract(zrxInstance.abi, zrxInstance.address, provider);
+ ck = new DummyERC721TokenContract(ckInstance.abi, ckInstance.address, provider);
+ et = new DummyERC721TokenContract(etInstance.abi, etInstance.address, provider);
const tokenTransferProxyInstance = await deployer.deployAsync(ContractName.TokenTransferProxy);
tokenTransferProxy = new TokenTransferProxyContract(
tokenTransferProxyInstance.abi,
tokenTransferProxyInstance.address,
provider,
);
+
+ const erc20TransferProxyV1Instance = await deployer.deployAsync(ContractName.ERC20TransferProxy_V1, [
+ tokenTransferProxy.address,
+ ]);
+ erc20TransferProxyV1 = new ERC20TransferProxy_v1Contract(
+ erc20TransferProxyV1Instance.abi,
+ erc20TransferProxyV1Instance.address,
+ provider,
+ );
+
+ const erc20TransferProxyInstance = await deployer.deployAsync(ContractName.ERC20TransferProxy);
+ erc20TransferProxy = new ERC20TransferProxyContract(
+ erc20TransferProxyInstance.abi,
+ erc20TransferProxyInstance.address,
+ provider,
+ );
+
+ erc721TransferProxyInstance = await deployer.deployAsync(ContractName.ERC721TransferProxy);
+ erc721TransferProxy = new ERC721TransferProxyContract(
+ erc721TransferProxyInstance.abi,
+ erc721TransferProxyInstance.address,
+ provider,
+ );
+
+ const assetProxyDispatcherInstance = await deployer.deployAsync(ContractName.AssetProxyDispatcher);
+ assetProxyDispatcher = new AssetProxyDispatcherContract(
+ assetProxyDispatcherInstance.abi,
+ assetProxyDispatcherInstance.address,
+ provider,
+ );
+
const exchangeInstance = await deployer.deployAsync(ContractName.Exchange, [
zrx.address,
- tokenTransferProxy.address,
+ encodeERC20ProxyMetadata(zrx.address),
+ assetProxyDispatcher.address,
]);
exchange = new ExchangeContract(exchangeInstance.abi, exchangeInstance.address, provider);
- await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: accounts[0] });
+ await assetProxyDispatcher.addAuthorizedAddress.sendTransactionAsync(assetProxyManagerAddress, {
+ from: accounts[0],
+ });
+ await assetProxyDispatcher.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: accounts[0] });
+ await erc20TransferProxyV1.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, {
+ from: accounts[0],
+ });
+ await erc20TransferProxy.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, {
+ from: accounts[0],
+ });
+ await erc721TransferProxy.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, {
+ from: accounts[0],
+ });
+ await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(erc20TransferProxyV1.address, {
+ from: accounts[0],
+ });
+ const nilAddress = '0x0000000000000000000000000000000000000000';
+ await assetProxyDispatcher.setAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20_V1,
+ erc20TransferProxyV1.address,
+ nilAddress,
+ { from: accounts[0] },
+ );
+ await assetProxyDispatcher.setAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20TransferProxy.address,
+ nilAddress,
+ { from: accounts[0] },
+ );
+ await assetProxyDispatcher.setAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC721,
+ erc721TransferProxy.address,
+ nilAddress,
+ { from: accounts[0] },
+ );
zeroEx = new ZeroEx(provider, {
exchangeContractAddress: exchange.address,
networkId: constants.TESTRPC_NETWORK_ID,
@@ -92,6 +188,8 @@ describe('Exchange', () => {
takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
+ makerAssetProxyData: encodeERC20ProxyMetadata(rep.address),
+ takerAssetProxyData: encodeERC20ProxyMetadata(dgd.address),
};
const privateKey = constants.TESTRPC_PRIVATE_KEYS[0];
orderFactory = new OrderFactory(privateKey, defaultOrderParams);
@@ -103,6 +201,12 @@ describe('Exchange', () => {
rep.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, {
from: takerAddress,
}),
+ rep.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, {
+ from: makerAddress,
+ }),
+ rep.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, {
+ from: takerAddress,
+ }),
rep.setBalance.sendTransactionAsync(makerAddress, INITIAL_BALANCE, { from: tokenOwner }),
rep.setBalance.sendTransactionAsync(takerAddress, INITIAL_BALANCE, { from: tokenOwner }),
dgd.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, {
@@ -111,6 +215,12 @@ describe('Exchange', () => {
dgd.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, {
from: takerAddress,
}),
+ dgd.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, {
+ from: makerAddress,
+ }),
+ dgd.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, {
+ from: takerAddress,
+ }),
dgd.setBalance.sendTransactionAsync(makerAddress, INITIAL_BALANCE, { from: tokenOwner }),
dgd.setBalance.sendTransactionAsync(takerAddress, INITIAL_BALANCE, { from: tokenOwner }),
zrx.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, {
@@ -119,8 +229,109 @@ describe('Exchange', () => {
zrx.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, {
from: takerAddress,
}),
+ zrx.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, {
+ from: makerAddress,
+ }),
+ zrx.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, {
+ from: takerAddress,
+ }),
zrx.setBalance.sendTransactionAsync(makerAddress, INITIAL_BALANCE, { from: tokenOwner }),
zrx.setBalance.sendTransactionAsync(takerAddress, INITIAL_BALANCE, { from: tokenOwner }),
+
+ // Distribute ck ERC721 tokens to maker & taker
+ // maker owns [0x1010.., ... , 0x4040..] and taker owns [0x5050.., ..., 0x9090..]
+ ck.setApprovalForAll.sendTransactionAsync(erc721TransferProxy.address, true, { from: makerAddress }),
+ ck.setApprovalForAll.sendTransactionAsync(erc721TransferProxy.address, true, { from: takerAddress }),
+ ck.mint.sendTransactionAsync(
+ makerAddress,
+ new BigNumber('0x1010101010101010101010101010101010101010101010101010101010101010'),
+ { from: tokenOwner },
+ ),
+ ck.mint.sendTransactionAsync(
+ makerAddress,
+ new BigNumber('0x2020202020202020202020202020202020202020202020202020202020202020'),
+ { from: tokenOwner },
+ ),
+ ck.mint.sendTransactionAsync(
+ makerAddress,
+ new BigNumber('0x3030303030303030303030303030303030303030303030303030303030303030'),
+ { from: tokenOwner },
+ ),
+ ck.mint.sendTransactionAsync(
+ makerAddress,
+ new BigNumber('0x4040404040404040404040404040404040404040404040404040404040404040'),
+ { from: tokenOwner },
+ ),
+ ck.mint.sendTransactionAsync(
+ takerAddress,
+ new BigNumber('0x5050505050505050505050505050505050505050505050505050505050505050'),
+ { from: tokenOwner },
+ ),
+ ck.mint.sendTransactionAsync(
+ takerAddress,
+ new BigNumber('0x6060606060606060606060606060606060606060606060606060606060606060'),
+ { from: tokenOwner },
+ ),
+ ck.mint.sendTransactionAsync(
+ takerAddress,
+ new BigNumber('0x7070707070707070707070707070707070707070707070707070707070707070'),
+ { from: tokenOwner },
+ ),
+ ck.mint.sendTransactionAsync(
+ takerAddress,
+ new BigNumber('0x8080808080808080808080808080808080808080808080808080808080808080'),
+ { from: tokenOwner },
+ ),
+ ck.mint.sendTransactionAsync(
+ takerAddress,
+ new BigNumber('0x9090909090909090909090909090909090909090909090909090909090909090'),
+ { from: tokenOwner },
+ ),
+
+ // Distribute et ERC721 tokens to maker & taker
+ // maker owns [0x1010.., ... , 0x4040..] and taker owns [0x5050.., ..., 0x9090..]
+ et.setApprovalForAll.sendTransactionAsync(erc721TransferProxy.address, true, { from: makerAddress }),
+ et.setApprovalForAll.sendTransactionAsync(erc721TransferProxy.address, true, { from: takerAddress }),
+ et.mint.sendTransactionAsync(
+ makerAddress,
+ new BigNumber('0x1010101010101010101010101010101010101010101010101010101010101010'),
+ { from: tokenOwner },
+ ),
+ et.mint.sendTransactionAsync(
+ makerAddress,
+ new BigNumber('0x2020202020202020202020202020202020202020202020202020202020202020'),
+ { from: tokenOwner },
+ ),
+ et.mint.sendTransactionAsync(
+ makerAddress,
+ new BigNumber('0x3030303030303030303030303030303030303030303030303030303030303030'),
+ { from: tokenOwner },
+ ),
+ et.mint.sendTransactionAsync(
+ takerAddress,
+ new BigNumber('0x5050505050505050505050505050505050505050505050505050505050505050'),
+ { from: tokenOwner },
+ ),
+ et.mint.sendTransactionAsync(
+ takerAddress,
+ new BigNumber('0x6060606060606060606060606060606060606060606060606060606060606060'),
+ { from: tokenOwner },
+ ),
+ et.mint.sendTransactionAsync(
+ takerAddress,
+ new BigNumber('0x7070707070707070707070707070707070707070707070707070707070707070'),
+ { from: tokenOwner },
+ ),
+ et.mint.sendTransactionAsync(
+ takerAddress,
+ new BigNumber('0x8080808080808080808080808080808080808080808080808080808080808080'),
+ { from: tokenOwner },
+ ),
+ et.mint.sendTransactionAsync(
+ takerAddress,
+ new BigNumber('0x9090909090909090909090909090909090909090909090909090909090909090'),
+ { from: tokenOwner },
+ ),
]);
});
beforeEach(async () => {
@@ -749,4 +960,284 @@ describe('Exchange', () => {
);
});
});
+
+ describe('Testing Exchange of ERC721 Tokens', () => {
+ it('should successfully exchange a single token between the maker and taker (via fillOrder)', async () => {
+ // Construct Exchange parameters
+ const makerTokenId = new BigNumber('0x1010101010101010101010101010101010101010101010101010101010101010');
+ const takerTokenId = new BigNumber('0x9090909090909090909090909090909090909090909090909090909090909090');
+ signedOrder = orderFactory.newSignedOrder({
+ makerTokenAddress: ck.address,
+ takerTokenAddress: ck.address,
+ makerTokenAmount: new BigNumber(1),
+ takerTokenAmount: new BigNumber(1),
+ makerAssetProxyData: encodeERC721ProxyMetadata(ck.address, makerTokenId),
+ takerAssetProxyData: encodeERC721ProxyMetadata(ck.address, takerTokenId),
+ });
+
+ // Verify pre-conditions
+ const initialOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId);
+ expect(initialOwnerMakerToken).to.be.bignumber.equal(makerAddress);
+ const initialOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId);
+ expect(initialOwnerTakerToken).to.be.bignumber.equal(takerAddress);
+
+ // Call Exchange
+ const takerTokenFillAmount = signedOrder.takerTokenAmount;
+ const res = await exWrapper.fillOrderAsync(signedOrder, takerAddress, { takerTokenFillAmount });
+
+ // Verify post-conditions
+ const newOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId);
+ expect(newOwnerMakerToken).to.be.bignumber.equal(takerAddress);
+ const newOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId);
+ expect(newOwnerTakerToken).to.be.bignumber.equal(makerAddress);
+ });
+
+ it('should successfully exchange a single token between the maker and taker (via filleOrderNoThrow)', async () => {
+ // Construct Exchange parameters
+ const makerTokenId = new BigNumber('0x1010101010101010101010101010101010101010101010101010101010101010');
+ const takerTokenId = new BigNumber('0x9090909090909090909090909090909090909090909090909090909090909090');
+ signedOrder = orderFactory.newSignedOrder({
+ makerTokenAddress: ck.address,
+ takerTokenAddress: ck.address,
+ makerTokenAmount: new BigNumber(1),
+ takerTokenAmount: new BigNumber(1),
+ makerAssetProxyData: encodeERC721ProxyMetadata(ck.address, makerTokenId),
+ takerAssetProxyData: encodeERC721ProxyMetadata(ck.address, takerTokenId),
+ });
+
+ // Verify pre-conditions
+ const initialOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId);
+ expect(initialOwnerMakerToken).to.be.bignumber.equal(makerAddress);
+ const initialOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId);
+ expect(initialOwnerTakerToken).to.be.bignumber.equal(takerAddress);
+
+ // Call Exchange
+ const takerTokenFillAmount = signedOrder.takerTokenAmount;
+ await exWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress, { takerTokenFillAmount });
+
+ // Verify post-conditions
+ const newOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId);
+ expect(newOwnerMakerToken).to.be.bignumber.equal(takerAddress);
+ const newOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId);
+ expect(newOwnerTakerToken).to.be.bignumber.equal(makerAddress);
+ });
+
+ it('should throw when maker does not own the token with id makerTokenId', async () => {
+ // Construct Exchange parameters
+ const makerTokenId = new BigNumber('0x5050505050505050505050505050505050505050505050505050505050505050');
+ const takerTokenId = new BigNumber('0x9090909090909090909090909090909090909090909090909090909090909090');
+ signedOrder = orderFactory.newSignedOrder({
+ makerTokenAddress: ck.address,
+ takerTokenAddress: ck.address,
+ makerTokenAmount: new BigNumber(1),
+ takerTokenAmount: new BigNumber(1),
+ makerAssetProxyData: encodeERC721ProxyMetadata(ck.address, makerTokenId),
+ takerAssetProxyData: encodeERC721ProxyMetadata(ck.address, takerTokenId),
+ });
+
+ // Verify pre-conditions
+ const initialOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId);
+ expect(initialOwnerMakerToken).to.be.bignumber.not.equal(makerAddress);
+ const initialOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId);
+ expect(initialOwnerTakerToken).to.be.bignumber.equal(takerAddress);
+
+ // Call Exchange
+ const takerTokenFillAmount = signedOrder.takerTokenAmount;
+ return expect(
+ exWrapper.fillOrderAsync(signedOrder, takerAddress, { takerTokenFillAmount }),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should throw when taker does not own the token with id takerTokenId', async () => {
+ // Construct Exchange parameters
+ const makerTokenId = new BigNumber('0x1010101010101010101010101010101010101010101010101010101010101010');
+ const takerTokenId = new BigNumber('0x2020202020202020202020202020202020202020202020202020202020202020');
+ signedOrder = orderFactory.newSignedOrder({
+ makerTokenAddress: ck.address,
+ takerTokenAddress: ck.address,
+ makerTokenAmount: new BigNumber(1),
+ takerTokenAmount: new BigNumber(1),
+ makerAssetProxyData: encodeERC721ProxyMetadata(ck.address, makerTokenId),
+ takerAssetProxyData: encodeERC721ProxyMetadata(ck.address, takerTokenId),
+ });
+
+ // Verify pre-conditions
+ const initialOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId);
+ expect(initialOwnerMakerToken).to.be.bignumber.equal(makerAddress);
+ const initialOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId);
+ expect(initialOwnerTakerToken).to.be.bignumber.not.equal(takerAddress);
+
+ // Call Exchange
+ const takerTokenFillAmount = signedOrder.takerTokenAmount;
+ return expect(
+ exWrapper.fillOrderAsync(signedOrder, takerAddress, { takerTokenFillAmount }),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should throw when makerTokenAmount is greater than 1', async () => {
+ // Construct Exchange parameters
+ const makerTokenId = new BigNumber('0x1010101010101010101010101010101010101010101010101010101010101010');
+ const takerTokenId = new BigNumber('0x9090909090909090909090909090909090909090909090909090909090909090');
+ signedOrder = orderFactory.newSignedOrder({
+ makerTokenAddress: ck.address,
+ takerTokenAddress: ck.address,
+ makerTokenAmount: new BigNumber(2),
+ takerTokenAmount: new BigNumber(1),
+ makerAssetProxyData: encodeERC721ProxyMetadata(ck.address, makerTokenId),
+ takerAssetProxyData: encodeERC721ProxyMetadata(ck.address, takerTokenId),
+ });
+
+ // Verify pre-conditions
+ const initialOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId);
+ expect(initialOwnerMakerToken).to.be.bignumber.equal(makerAddress);
+ const initialOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId);
+ expect(initialOwnerTakerToken).to.be.bignumber.equal(takerAddress);
+
+ // Call Exchange
+ const takerTokenFillAmount = signedOrder.takerTokenAmount;
+ return expect(
+ exWrapper.fillOrderAsync(signedOrder, takerAddress, { takerTokenFillAmount }),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should throw when takerTokenAmount is greater than 1', async () => {
+ // Construct Exchange parameters
+ const makerTokenId = new BigNumber('0x1010101010101010101010101010101010101010101010101010101010101010');
+ const takerTokenId = new BigNumber('0x9090909090909090909090909090909090909090909090909090909090909090');
+ signedOrder = orderFactory.newSignedOrder({
+ makerTokenAddress: ck.address,
+ takerTokenAddress: ck.address,
+ makerTokenAmount: new BigNumber(1),
+ takerTokenAmount: new BigNumber(500),
+ makerAssetProxyData: encodeERC721ProxyMetadata(ck.address, makerTokenId),
+ takerAssetProxyData: encodeERC721ProxyMetadata(ck.address, takerTokenId),
+ });
+
+ // Verify pre-conditions
+ const initialOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId);
+ expect(initialOwnerMakerToken).to.be.bignumber.equal(makerAddress);
+ const initialOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId);
+ expect(initialOwnerTakerToken).to.be.bignumber.equal(takerAddress);
+
+ // Call Exchange
+ const takerTokenFillAmount = signedOrder.takerTokenAmount;
+ return expect(
+ exWrapper.fillOrderAsync(signedOrder, takerAddress, { takerTokenFillAmount }),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should throw on partial fill', async () => {
+ // Construct Exchange parameters
+ const makerTokenId = new BigNumber('0x1010101010101010101010101010101010101010101010101010101010101010');
+ const takerTokenId = new BigNumber('0x9090909090909090909090909090909090909090909090909090909090909090');
+ signedOrder = orderFactory.newSignedOrder({
+ makerTokenAddress: ck.address,
+ takerTokenAddress: ck.address,
+ makerTokenAmount: new BigNumber(1),
+ takerTokenAmount: new BigNumber(0),
+ makerAssetProxyData: encodeERC721ProxyMetadata(ck.address, makerTokenId),
+ takerAssetProxyData: encodeERC721ProxyMetadata(ck.address, takerTokenId),
+ });
+
+ // Verify pre-conditions
+ const initialOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId);
+ expect(initialOwnerMakerToken).to.be.bignumber.equal(makerAddress);
+ const initialOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId);
+ expect(initialOwnerTakerToken).to.be.bignumber.equal(takerAddress);
+
+ // Call Exchange
+ const takerTokenFillAmount = signedOrder.takerTokenAmount;
+ return expect(
+ exWrapper.fillOrderAsync(signedOrder, takerAddress, { takerTokenFillAmount }),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should successfully fill order when makerToken is ERC721 and takerToken is ERC20', async () => {
+ // Construct Exchange parameters
+ const makerTokenId = new BigNumber('0x1010101010101010101010101010101010101010101010101010101010101010');
+ signedOrder = orderFactory.newSignedOrder({
+ makerTokenAddress: ck.address,
+ takerTokenAddress: dgd.address,
+ makerTokenAmount: new BigNumber(1),
+ takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ makerAssetProxyData: encodeERC721ProxyMetadata(ck.address, makerTokenId),
+ takerAssetProxyData: encodeERC20ProxyMetadata(dgd.address),
+ });
+
+ // Verify pre-conditions
+ const initialOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId);
+ expect(initialOwnerMakerToken).to.be.bignumber.equal(makerAddress);
+
+ // Call Exchange
+ balances = await dmyBalances.getAsync();
+ const takerTokenFillAmount = signedOrder.takerTokenAmount;
+ await exWrapper.fillOrderAsync(signedOrder, takerAddress, { takerTokenFillAmount });
+
+ // Verify ERC721 token was transferred from Maker to Taker
+ const newOwnerMakerToken = await ck.ownerOf.callAsync(makerTokenId);
+ expect(newOwnerMakerToken).to.be.bignumber.equal(takerAddress);
+
+ // Verify ERC20 tokens were transferred from Taker to Maker & fees were paid correctly
+ const newBalances = await dmyBalances.getAsync();
+ expect(newBalances[makerAddress][signedOrder.takerTokenAddress]).to.be.bignumber.equal(
+ balances[makerAddress][signedOrder.takerTokenAddress].add(takerTokenFillAmount),
+ );
+ expect(newBalances[takerAddress][signedOrder.takerTokenAddress]).to.be.bignumber.equal(
+ balances[takerAddress][signedOrder.takerTokenAddress].minus(takerTokenFillAmount),
+ );
+ expect(newBalances[makerAddress][zrx.address]).to.be.bignumber.equal(
+ balances[makerAddress][zrx.address].minus(signedOrder.makerFee),
+ );
+ expect(newBalances[takerAddress][zrx.address]).to.be.bignumber.equal(
+ balances[takerAddress][zrx.address].minus(signedOrder.takerFee),
+ );
+ expect(newBalances[feeRecipientAddress][zrx.address]).to.be.bignumber.equal(
+ balances[feeRecipientAddress][zrx.address].add(signedOrder.makerFee.add(signedOrder.takerFee)),
+ );
+ });
+
+ it('should successfully fill order when makerToken is ERC20 and takerToken is ERC721', async () => {
+ // Construct Exchange parameters
+ const takerTokenId = new BigNumber('0x9090909090909090909090909090909090909090909090909090909090909090');
+ signedOrder = orderFactory.newSignedOrder({
+ takerTokenAddress: ck.address,
+ makerTokenAddress: dgd.address,
+ takerTokenAmount: new BigNumber(1),
+ makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ takerAssetProxyData: encodeERC721ProxyMetadata(ck.address, takerTokenId),
+ makerAssetProxyData: encodeERC20ProxyMetadata(dgd.address),
+ });
+
+ // Verify pre-conditions
+ const initialOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId);
+ expect(initialOwnerTakerToken).to.be.bignumber.equal(takerAddress);
+
+ // Call Exchange
+ balances = await dmyBalances.getAsync();
+ const takerTokenFillAmount = signedOrder.takerTokenAmount;
+ await exWrapper.fillOrderAsync(signedOrder, takerAddress, { takerTokenFillAmount });
+
+ // Verify ERC721 token was transferred from Taker to Maker
+ const newOwnerTakerToken = await ck.ownerOf.callAsync(takerTokenId);
+ expect(newOwnerTakerToken).to.be.bignumber.equal(makerAddress);
+
+ // Verify ERC20 tokens were transferred from Maker to Taker & fees were paid correctly
+ const newBalances = await dmyBalances.getAsync();
+ expect(newBalances[takerAddress][signedOrder.makerTokenAddress]).to.be.bignumber.equal(
+ balances[takerAddress][signedOrder.makerTokenAddress].add(signedOrder.makerTokenAmount),
+ );
+ expect(newBalances[makerAddress][signedOrder.makerTokenAddress]).to.be.bignumber.equal(
+ balances[makerAddress][signedOrder.makerTokenAddress].minus(signedOrder.makerTokenAmount),
+ );
+ expect(newBalances[makerAddress][zrx.address]).to.be.bignumber.equal(
+ balances[makerAddress][zrx.address].minus(signedOrder.makerFee),
+ );
+ expect(newBalances[takerAddress][zrx.address]).to.be.bignumber.equal(
+ balances[takerAddress][zrx.address].minus(signedOrder.takerFee),
+ );
+ expect(newBalances[feeRecipientAddress][zrx.address]).to.be.bignumber.equal(
+ balances[feeRecipientAddress][zrx.address].add(signedOrder.makerFee.add(signedOrder.takerFee)),
+ );
+ });
+ });
}); // tslint:disable-line:max-file-line-count
diff --git a/packages/contracts/test/exchange/helpers.ts b/packages/contracts/test/exchange/helpers.ts
index 4fa55efa1..37e53630e 100644
--- a/packages/contracts/test/exchange/helpers.ts
+++ b/packages/contracts/test/exchange/helpers.ts
@@ -6,11 +6,16 @@ import * as chai from 'chai';
import ethUtil = require('ethereumjs-util');
import { ExchangeContract } from '../../src/contract_wrappers/generated/exchange';
+import {
+ encodeERC20ProxyMetadata,
+ encodeERC20ProxyMetadata_V1,
+ encodeERC721ProxyMetadata,
+} from '../../src/utils/asset_proxy_utils';
import { constants } from '../../src/utils/constants';
import { ExchangeWrapper } from '../../src/utils/exchange_wrapper';
import { OrderFactory } from '../../src/utils/order_factory';
import { orderUtils } from '../../src/utils/order_utils';
-import { ContractName, SignedOrder } from '../../src/utils/types';
+import { AssetProxyId, ContractName, SignedOrder } from '../../src/utils/types';
import { chaiSetup } from '../utils/chai_setup';
import { deployer } from '../utils/deployer';
import { provider, web3Wrapper } from '../utils/web3_wrapper';
@@ -23,6 +28,7 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('Exchange', () => {
let makerAddress: string;
let feeRecipientAddress: string;
+ let assetProxyManagerAddress: string;
let signedOrder: SignedOrder;
let exchangeWrapper: ExchangeWrapper;
@@ -30,9 +36,14 @@ describe('Exchange', () => {
before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
- [makerAddress, feeRecipientAddress] = accounts;
+ [makerAddress, feeRecipientAddress, assetProxyManagerAddress] = accounts;
const tokenRegistry = await deployer.deployAsync(ContractName.TokenRegistry);
const tokenTransferProxy = await deployer.deployAsync(ContractName.TokenTransferProxy);
+ const assetProxyDispatcher = await deployer.deployAsync(ContractName.AssetProxyDispatcher);
+ const erc20TransferProxyV1 = await deployer.deployAsync(ContractName.ERC20TransferProxy_V1, [
+ tokenTransferProxy.address,
+ ]);
+ const erc20TransferProxy = await deployer.deployAsync(ContractName.ERC20TransferProxy);
const [rep, dgd, zrx] = await Promise.all([
deployer.deployAsync(ContractName.DummyToken, constants.DUMMY_TOKEN_ARGS),
deployer.deployAsync(ContractName.DummyToken, constants.DUMMY_TOKEN_ARGS),
@@ -40,10 +51,36 @@ describe('Exchange', () => {
]);
const exchangeInstance = await deployer.deployAsync(ContractName.Exchange, [
zrx.address,
- tokenTransferProxy.address,
+ AssetProxyId.ERC20,
+ assetProxyDispatcher.address,
]);
const exchange = new ExchangeContract(exchangeInstance.abi, exchangeInstance.address, provider);
- await tokenTransferProxy.addAuthorizedAddress(exchange.address, { from: accounts[0] });
+ await assetProxyDispatcher.addAuthorizedAddress.sendTransactionAsync(assetProxyManagerAddress, {
+ from: accounts[0],
+ });
+ await assetProxyDispatcher.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: accounts[0] });
+ await erc20TransferProxyV1.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, {
+ from: accounts[0],
+ });
+ await erc20TransferProxy.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, {
+ from: accounts[0],
+ });
+ await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(erc20TransferProxyV1.address, {
+ from: accounts[0],
+ });
+ const nilAddress = '0x0000000000000000000000000000000000000000';
+ await assetProxyDispatcher.setAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20_V1,
+ erc20TransferProxyV1.address,
+ nilAddress,
+ { from: accounts[0] },
+ );
+ await assetProxyDispatcher.setAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20TransferProxy.address,
+ nilAddress,
+ { from: accounts[0] },
+ );
const zeroEx = new ZeroEx(provider, { networkId: constants.TESTRPC_NETWORK_ID });
exchangeWrapper = new ExchangeWrapper(exchange, zeroEx);
const defaultOrderParams = {
@@ -56,6 +93,8 @@ describe('Exchange', () => {
takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
+ makerAssetProxyData: encodeERC20ProxyMetadata(rep.address),
+ takerAssetProxyData: encodeERC20ProxyMetadata(dgd.address),
};
const privateKey = constants.TESTRPC_PRIVATE_KEYS[0];
orderFactory = new OrderFactory(privateKey, defaultOrderParams);
diff --git a/packages/contracts/test/exchange/wrapper.ts b/packages/contracts/test/exchange/wrapper.ts
index 0ba74e1e9..a58568adb 100644
--- a/packages/contracts/test/exchange/wrapper.ts
+++ b/packages/contracts/test/exchange/wrapper.ts
@@ -6,15 +6,23 @@ import * as chai from 'chai';
import * as _ from 'lodash';
import * as Web3 from 'web3';
+import { AssetProxyDispatcherContract } from '../../src/contract_wrappers/generated/asset_proxy_dispatcher';
import { DummyTokenContract } from '../../src/contract_wrappers/generated/dummy_token';
+import { ERC20TransferProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_transfer_proxy';
+import { ERC20TransferProxy_v1Contract } from '../../src/contract_wrappers/generated/erc20transferproxy_v1';
import { ExchangeContract } from '../../src/contract_wrappers/generated/exchange';
import { TokenRegistryContract } from '../../src/contract_wrappers/generated/token_registry';
import { TokenTransferProxyContract } from '../../src/contract_wrappers/generated/token_transfer_proxy';
+import {
+ encodeERC20ProxyMetadata,
+ encodeERC20ProxyMetadata_V1,
+ encodeERC721ProxyMetadata,
+} from '../../src/utils/asset_proxy_utils';
import { Balances } from '../../src/utils/balances';
import { constants } from '../../src/utils/constants';
import { ExchangeWrapper } from '../../src/utils/exchange_wrapper';
import { OrderFactory } from '../../src/utils/order_factory';
-import { BalancesByOwner, ContractName, SignedOrder } from '../../src/utils/types';
+import { AssetProxyId, BalancesByOwner, ContractName, SignedOrder } from '../../src/utils/types';
import { chaiSetup } from '../utils/chai_setup';
import { deployer } from '../utils/deployer';
import { provider, web3Wrapper } from '../utils/web3_wrapper';
@@ -28,6 +36,7 @@ describe('Exchange', () => {
let tokenOwner: string;
let takerAddress: string;
let feeRecipientAddress: string;
+ let assetProxyManagerAddress: string;
const INITIAL_BALANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18);
const INITIAL_ALLOWANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18);
@@ -38,6 +47,9 @@ describe('Exchange', () => {
let exchange: ExchangeContract;
let tokenRegistry: TokenRegistryContract;
let tokenTransferProxy: TokenTransferProxyContract;
+ let assetProxyDispatcher: AssetProxyDispatcherContract;
+ let erc20TransferProxyV1: ERC20TransferProxy_v1Contract;
+ let erc20TransferProxy: ERC20TransferProxyContract;
let balances: BalancesByOwner;
@@ -48,7 +60,7 @@ describe('Exchange', () => {
before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
tokenOwner = accounts[0];
- [makerAddress, takerAddress, feeRecipientAddress] = accounts;
+ [makerAddress, takerAddress, feeRecipientAddress, assetProxyManagerAddress] = accounts;
const [repInstance, dgdInstance, zrxInstance] = await Promise.all([
deployer.deployAsync(ContractName.DummyToken, constants.DUMMY_TOKEN_ARGS),
deployer.deployAsync(ContractName.DummyToken, constants.DUMMY_TOKEN_ARGS),
@@ -65,12 +77,58 @@ describe('Exchange', () => {
tokenTransferProxyInstance.address,
provider,
);
+ const erc20TransferProxyV1Instance = await deployer.deployAsync(ContractName.ERC20TransferProxy_V1, [
+ tokenTransferProxy.address,
+ ]);
+ erc20TransferProxyV1 = new ERC20TransferProxy_v1Contract(
+ erc20TransferProxyV1Instance.abi,
+ erc20TransferProxyV1Instance.address,
+ provider,
+ );
+ const erc20TransferProxyInstance = await deployer.deployAsync(ContractName.ERC20TransferProxy);
+ erc20TransferProxy = new ERC20TransferProxyContract(
+ erc20TransferProxyInstance.abi,
+ erc20TransferProxyInstance.address,
+ provider,
+ );
+ const assetProxyDispatcherInstance = await deployer.deployAsync(ContractName.AssetProxyDispatcher);
+ assetProxyDispatcher = new AssetProxyDispatcherContract(
+ assetProxyDispatcherInstance.abi,
+ assetProxyDispatcherInstance.address,
+ provider,
+ );
const exchangeInstance = await deployer.deployAsync(ContractName.Exchange, [
zrx.address,
- tokenTransferProxy.address,
+ encodeERC20ProxyMetadata(zrx.address),
+ assetProxyDispatcher.address,
]);
exchange = new ExchangeContract(exchangeInstance.abi, exchangeInstance.address, provider);
- await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: accounts[0] });
+ await assetProxyDispatcher.addAuthorizedAddress.sendTransactionAsync(assetProxyManagerAddress, {
+ from: accounts[0],
+ });
+ await assetProxyDispatcher.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: accounts[0] });
+ await erc20TransferProxyV1.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, {
+ from: accounts[0],
+ });
+ await erc20TransferProxy.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, {
+ from: accounts[0],
+ });
+ await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(erc20TransferProxyV1.address, {
+ from: accounts[0],
+ });
+ const nilAddress = '0x0000000000000000000000000000000000000000';
+ await assetProxyDispatcher.setAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20_V1,
+ erc20TransferProxyV1.address,
+ nilAddress,
+ { from: accounts[0] },
+ );
+ await assetProxyDispatcher.setAssetProxy.sendTransactionAsync(
+ AssetProxyId.ERC20,
+ erc20TransferProxy.address,
+ nilAddress,
+ { from: accounts[0] },
+ );
const zeroEx = new ZeroEx(provider, { networkId: constants.TESTRPC_NETWORK_ID });
exWrapper = new ExchangeWrapper(exchange, zeroEx);
@@ -84,6 +142,8 @@ describe('Exchange', () => {
takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
+ makerAssetProxyData: encodeERC20ProxyMetadata(rep.address),
+ takerAssetProxyData: encodeERC20ProxyMetadata(dgd.address),
};
const privateKey = constants.TESTRPC_PRIVATE_KEYS[0];
@@ -92,14 +152,20 @@ describe('Exchange', () => {
await Promise.all([
rep.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: makerAddress }),
rep.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: takerAddress }),
+ rep.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, { from: makerAddress }),
+ rep.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, { from: takerAddress }),
rep.setBalance.sendTransactionAsync(makerAddress, INITIAL_BALANCE, { from: tokenOwner }),
rep.setBalance.sendTransactionAsync(takerAddress, INITIAL_BALANCE, { from: tokenOwner }),
dgd.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: makerAddress }),
dgd.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: takerAddress }),
+ dgd.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, { from: makerAddress }),
+ dgd.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, { from: takerAddress }),
dgd.setBalance.sendTransactionAsync(makerAddress, INITIAL_BALANCE, { from: tokenOwner }),
dgd.setBalance.sendTransactionAsync(takerAddress, INITIAL_BALANCE, { from: tokenOwner }),
zrx.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: makerAddress }),
zrx.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: takerAddress }),
+ zrx.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, { from: makerAddress }),
+ zrx.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, { from: takerAddress }),
zrx.setBalance.sendTransactionAsync(makerAddress, INITIAL_BALANCE, { from: tokenOwner }),
zrx.setBalance.sendTransactionAsync(takerAddress, INITIAL_BALANCE, { from: tokenOwner }),
]);
@@ -246,11 +312,11 @@ describe('Exchange', () => {
it('should not change balances if maker allowances are too low to fill order', async () => {
const signedOrder = orderFactory.newSignedOrder();
- await rep.approve.sendTransactionAsync(tokenTransferProxy.address, new BigNumber(0), {
+ await rep.approve.sendTransactionAsync(erc20TransferProxy.address, new BigNumber(0), {
from: makerAddress,
});
await exWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress);
- await rep.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, {
+ await rep.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, {
from: makerAddress,
});
@@ -260,11 +326,11 @@ describe('Exchange', () => {
it('should not change balances if taker allowances are too low to fill order', async () => {
const signedOrder = orderFactory.newSignedOrder();
- await dgd.approve.sendTransactionAsync(tokenTransferProxy.address, new BigNumber(0), {
+ await dgd.approve.sendTransactionAsync(erc20TransferProxy.address, new BigNumber(0), {
from: takerAddress,
});
await exWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress);
- await dgd.approve.sendTransactionAsync(tokenTransferProxy.address, INITIAL_ALLOWANCE, {
+ await dgd.approve.sendTransactionAsync(erc20TransferProxy.address, INITIAL_ALLOWANCE, {
from: takerAddress,
});
@@ -278,6 +344,7 @@ describe('Exchange', () => {
makerTokenAddress: zrx.address,
makerTokenAmount: makerZRXBalance,
makerFee: new BigNumber(1),
+ makerAssetProxyData: encodeERC20ProxyMetadata(zrx.address),
});
await exWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress);
const newBalances = await dmyBalances.getAsync();
@@ -285,11 +352,12 @@ describe('Exchange', () => {
});
it('should not change balances if makerTokenAddress is ZRX, makerTokenAmount + makerFee > maker allowance', async () => {
- const makerZRXAllowance = await zrx.allowance.callAsync(makerAddress, tokenTransferProxy.address);
+ const makerZRXAllowance = await zrx.allowance.callAsync(makerAddress, erc20TransferProxy.address);
const signedOrder = orderFactory.newSignedOrder({
makerTokenAddress: zrx.address,
makerTokenAmount: new BigNumber(makerZRXAllowance),
makerFee: new BigNumber(1),
+ makerAssetProxyData: encodeERC20ProxyMetadata(zrx.address),
});
await exWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress);
const newBalances = await dmyBalances.getAsync();
@@ -302,6 +370,7 @@ describe('Exchange', () => {
takerTokenAddress: zrx.address,
takerTokenAmount: takerZRXBalance,
takerFee: new BigNumber(1),
+ takerAssetProxyData: encodeERC20ProxyMetadata(zrx.address),
});
await exWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress);
const newBalances = await dmyBalances.getAsync();
@@ -309,11 +378,12 @@ describe('Exchange', () => {
});
it('should not change balances if takerTokenAddress is ZRX, takerTokenAmount + takerFee > taker allowance', async () => {
- const takerZRXAllowance = await zrx.allowance.callAsync(takerAddress, tokenTransferProxy.address);
+ const takerZRXAllowance = await zrx.allowance.callAsync(takerAddress, erc20TransferProxy.address);
const signedOrder = orderFactory.newSignedOrder({
takerTokenAddress: zrx.address,
takerTokenAmount: new BigNumber(takerZRXAllowance),
takerFee: new BigNumber(1),
+ takerAssetProxyData: encodeERC20ProxyMetadata(zrx.address),
});
await exWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress);
const newBalances = await dmyBalances.getAsync();