Asset Proxy Dispatcher

This commit is contained in:
Greg Hysen 2018-04-10 17:53:34 -07:00 committed by Amir Bandeali
parent b9e0cd4512
commit 78d81f193f
30 changed files with 2468 additions and 159 deletions

View File

@ -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"
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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()
{}
}

View File

@ -54,11 +54,6 @@ contract IExchange {
bytes32 indexed orderHash
);
event LogCancelBefore(
address indexed maker,
uint256 salt
);
function ZRX_TOKEN_CONTRACT()
public view
returns (address);

View File

@ -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;

View File

@ -36,5 +36,3 @@ contract LibPartialAmount is SafeMath {
return partialAmount;
}
}

View File

@ -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.makerAssetProxyData,
order.makerAddress,
takerAddress,
makerTokenFilledAmount
)
);
require(
TRANSFER_PROXY.transferFrom(
order.takerTokenAddress,
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,
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,
ZRX_PROXY_METADATA,
takerAddress,
order.feeRecipientAddress,
takerFeePaid
)
);
}
}

View File

@ -66,74 +66,161 @@ contract MixinWrapperFunctions is
// 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 |
// | 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 |
// (1): len(signature)
// (2): 452 + len(signature)
// (3): (32 - len(signature)) mod 32
// (4): 452 + len(signature) + (32 - len(signature)) mod 32
// * 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)
let headerAreaStart := mload(0x40)
mstore(headerAreaStart, fillOrderSelector)
let headerAreaEnd := add(headerAreaStart, 0x4)
// Write function signature
mstore(start, fillOrderSelector)
/////// 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
// 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
/////// Setup Data Area ///////
let dataAreaStart := paramsAreaEnd
let dataAreaEnd := dataAreaStart
// Write takerTokenFillAmount
mstore(add(start, 356), takerTokenFillAmount)
// 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 signature offset
mstore(add(start, 388), 416)
/////// 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 signature length
let sigLen := mload(signature)
mstore(add(start, 420), sigLen)
// 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)
}
// Calculate signature length with padding
let paddingLen := mod(sub(0, sigLen), 32)
let sigLenWithPadding := add(sigLen, paddingLen)
// Write offset to <order.makerAssetProxyMetadata>
mstore(add(dataAreaStart, mul(11, 0x20)), sub(dataAreaEnd, dataAreaStart))
// 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
// 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
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
@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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)
}
}
}

View File

@ -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;
}

View File

@ -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()

View File

@ -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;
}

View File

@ -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)),
};

View File

@ -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 {

View File

@ -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')}`;

View File

@ -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 {

View File

@ -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);
});
});
});

View File

@ -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);
});
});
});

View File

@ -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 exchangeInstance = await deployer.deployAsync(ContractName.Exchange, [
zrx.address,
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,
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

View File

@ -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);

View File

@ -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 exchangeInstance = await deployer.deployAsync(ContractName.Exchange, [
zrx.address,
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,
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();

View File

@ -1731,10 +1731,6 @@ buffer-from@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-0.1.2.tgz#15f4b9bcef012044df31142c14333caf6e0260d0"
buffer-from@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531"
buffer-indexof@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c"
@ -2313,10 +2309,9 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
concat-stream@^1.4.10, concat-stream@^1.5.0:
version "1.6.2"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
version "1.6.1"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.1.tgz#261b8f518301f1d834e36342b9fea095d2620a26"
dependencies:
buffer-from "^1.0.0"
inherits "^2.0.3"
readable-stream "^2.2.2"
typedarray "^0.0.6"
@ -2570,8 +2565,8 @@ core-js@^1.0.0:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
core-js@^2.4.0, core-js@^2.5.0:
version "2.5.5"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.5.tgz#b14dde936c640c0579a6b50cabcc132dd6127e3b"
version "2.5.3"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e"
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
@ -3955,8 +3950,8 @@ extend@^3.0.0, extend@~3.0.0, extend@~3.0.1:
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
external-editor@^2.0.4:
version "2.2.0"
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5"
version "2.1.0"
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.1.0.tgz#3d026a21b7f95b5726387d4200ac160d372c3b48"
dependencies:
chardet "^0.4.0"
iconv-lite "^0.4.17"
@ -9402,14 +9397,14 @@ semver-sort@0.0.4:
semver "^5.0.3"
semver-regex "^1.0.0"
"semver@2 || 3 || 4 || 5", semver@5.5.0, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
semver@5.4.1, semver@~5.4.1:
"semver@2 || 3 || 4 || 5", semver@5.4.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@~5.4.1:
version "5.4.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
semver@5.5.0, semver@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
semver@^4.1.0:
version "4.3.6"
resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da"
@ -10250,8 +10245,8 @@ swarm-js@0.1.37:
xhr-request-promise "^0.1.2"
symbol-observable@^1.0.3, symbol-observable@^1.0.4:
version "1.2.0"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
version "1.1.0"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.1.0.tgz#5c68fd8d54115d9dfb72a84720549222e8db9b32"
symbol@~0.3.1:
version "0.3.1"
@ -12006,7 +12001,7 @@ yauzl@^2.4.2:
buffer-crc32 "~0.2.3"
fd-slicer "~1.0.1"
zeppelin-solidity@1.8.0:
zeppelin-solidity@1.8.0, zeppelin-solidity@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/zeppelin-solidity/-/zeppelin-solidity-1.8.0.tgz#049fcde7daea9fc85210f8c6db9f8cd1ab8a853a"
dependencies: