Merge pull request #743 from 0xProject/feature/contracts/abi-encoded
ABI encode assetData fields
This commit is contained in:
@@ -27,6 +27,7 @@
|
|||||||
"ERC721Proxy",
|
"ERC721Proxy",
|
||||||
"Exchange",
|
"Exchange",
|
||||||
"ExchangeWrapper",
|
"ExchangeWrapper",
|
||||||
|
"IAssetData",
|
||||||
"MixinAuthorizable",
|
"MixinAuthorizable",
|
||||||
"MultiSigWallet",
|
"MultiSigWallet",
|
||||||
"MultiSigWalletWithTimeLock",
|
"MultiSigWalletWithTimeLock",
|
||||||
|
@@ -34,7 +34,7 @@
|
|||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"abis":
|
"abis":
|
||||||
"../migrations/artifacts/2.0.0/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|ERC20Proxy|ERC721Proxy|Exchange|ExchangeWrapper|MixinAuthorizable|MultiSigWallet|MultiSigWalletWithTimeLock|TestAssetProxyOwner|TestAssetDataDecoders|TestAssetProxyDispatcher|TestLibBytes|TestLibs|TestSignatureValidator|TestValidator|TestWallet|TokenRegistry|Whitelist|WETH9|ZRXToken).json"
|
"../migrations/artifacts/2.0.0/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|ERC20Proxy|ERC721Proxy|Exchange|ExchangeWrapper|IAssetData|MixinAuthorizable|MultiSigWallet|MultiSigWalletWithTimeLock|TestAssetProxyOwner|TestAssetDataDecoders|TestAssetProxyDispatcher|TestLibBytes|TestLibs|TestSignatureValidator|TestValidator|TestWallet|TokenRegistry|Whitelist|WETH9|ZRXToken).json"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@@ -30,14 +30,14 @@ contract ERC20Proxy is
|
|||||||
MixinERC20Transfer
|
MixinERC20Transfer
|
||||||
{
|
{
|
||||||
// Id of this proxy.
|
// Id of this proxy.
|
||||||
uint8 constant PROXY_ID = 1;
|
bytes4 constant PROXY_ID = bytes4(keccak256("ERC20Token(address)"));
|
||||||
|
|
||||||
/// @dev Gets the proxy id associated with the proxy address.
|
/// @dev Gets the proxy id associated with the proxy address.
|
||||||
/// @return Proxy id.
|
/// @return Proxy id.
|
||||||
function getProxyId()
|
function getProxyId()
|
||||||
external
|
external
|
||||||
view
|
view
|
||||||
returns (uint8)
|
returns (bytes4)
|
||||||
{
|
{
|
||||||
return PROXY_ID;
|
return PROXY_ID;
|
||||||
}
|
}
|
||||||
|
@@ -30,14 +30,14 @@ contract ERC721Proxy is
|
|||||||
MixinERC721Transfer
|
MixinERC721Transfer
|
||||||
{
|
{
|
||||||
// Id of this proxy.
|
// Id of this proxy.
|
||||||
uint8 constant PROXY_ID = 2;
|
bytes4 constant PROXY_ID = bytes4(keccak256("ERC721Token(address,uint256,bytes)"));
|
||||||
|
|
||||||
/// @dev Gets the proxy id associated with the proxy address.
|
/// @dev Gets the proxy id associated with the proxy address.
|
||||||
/// @return Proxy id.
|
/// @return Proxy id.
|
||||||
function getProxyId()
|
function getProxyId()
|
||||||
external
|
external
|
||||||
view
|
view
|
||||||
returns (uint8)
|
returns (bytes4)
|
||||||
{
|
{
|
||||||
return PROXY_ID;
|
return PROXY_ID;
|
||||||
}
|
}
|
||||||
|
@@ -19,12 +19,10 @@
|
|||||||
pragma solidity ^0.4.24;
|
pragma solidity ^0.4.24;
|
||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
import "./libs/LibAssetProxyErrors.sol";
|
|
||||||
import "../../utils/Ownable/Ownable.sol";
|
import "../../utils/Ownable/Ownable.sol";
|
||||||
import "./mixins/MAuthorizable.sol";
|
import "./mixins/MAuthorizable.sol";
|
||||||
|
|
||||||
contract MixinAuthorizable is
|
contract MixinAuthorizable is
|
||||||
LibAssetProxyErrors,
|
|
||||||
Ownable,
|
Ownable,
|
||||||
MAuthorizable
|
MAuthorizable
|
||||||
{
|
{
|
||||||
@@ -33,7 +31,7 @@ contract MixinAuthorizable is
|
|||||||
modifier onlyAuthorized {
|
modifier onlyAuthorized {
|
||||||
require(
|
require(
|
||||||
authorized[msg.sender],
|
authorized[msg.sender],
|
||||||
SENDER_NOT_AUTHORIZED
|
"SENDER_NOT_AUTHORIZED"
|
||||||
);
|
);
|
||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
@@ -49,7 +47,7 @@ contract MixinAuthorizable is
|
|||||||
{
|
{
|
||||||
require(
|
require(
|
||||||
!authorized[target],
|
!authorized[target],
|
||||||
TARGET_ALREADY_AUTHORIZED
|
"TARGET_ALREADY_AUTHORIZED"
|
||||||
);
|
);
|
||||||
|
|
||||||
authorized[target] = true;
|
authorized[target] = true;
|
||||||
@@ -65,7 +63,7 @@ contract MixinAuthorizable is
|
|||||||
{
|
{
|
||||||
require(
|
require(
|
||||||
authorized[target],
|
authorized[target],
|
||||||
TARGET_NOT_AUTHORIZED
|
"TARGET_NOT_AUTHORIZED"
|
||||||
);
|
);
|
||||||
|
|
||||||
delete authorized[target];
|
delete authorized[target];
|
||||||
@@ -91,15 +89,15 @@ contract MixinAuthorizable is
|
|||||||
{
|
{
|
||||||
require(
|
require(
|
||||||
authorized[target],
|
authorized[target],
|
||||||
TARGET_NOT_AUTHORIZED
|
"TARGET_NOT_AUTHORIZED"
|
||||||
);
|
);
|
||||||
require(
|
require(
|
||||||
index < authorities.length,
|
index < authorities.length,
|
||||||
INDEX_OUT_OF_BOUNDS
|
"INDEX_OUT_OF_BOUNDS"
|
||||||
);
|
);
|
||||||
require(
|
require(
|
||||||
authorities[index] == target,
|
authorities[index] == target,
|
||||||
AUTHORIZED_ADDRESS_MISMATCH
|
"AUTHORIZED_ADDRESS_MISMATCH"
|
||||||
);
|
);
|
||||||
|
|
||||||
delete authorized[target];
|
delete authorized[target];
|
||||||
|
@@ -21,11 +21,9 @@ pragma experimental ABIEncoderV2;
|
|||||||
|
|
||||||
import "../../utils/LibBytes/LibBytes.sol";
|
import "../../utils/LibBytes/LibBytes.sol";
|
||||||
import "../../tokens/ERC20Token/IERC20Token.sol";
|
import "../../tokens/ERC20Token/IERC20Token.sol";
|
||||||
import "./libs/LibTransferErrors.sol";
|
|
||||||
|
|
||||||
contract MixinERC20Transfer is
|
contract MixinERC20Transfer {
|
||||||
LibTransferErrors
|
|
||||||
{
|
|
||||||
using LibBytes for bytes;
|
using LibBytes for bytes;
|
||||||
|
|
||||||
/// @dev Internal version of `transferFrom`.
|
/// @dev Internal version of `transferFrom`.
|
||||||
@@ -42,41 +40,74 @@ contract MixinERC20Transfer is
|
|||||||
internal
|
internal
|
||||||
{
|
{
|
||||||
// Decode asset data.
|
// Decode asset data.
|
||||||
address token = assetData.readAddress(0);
|
address token = assetData.readAddress(16);
|
||||||
|
|
||||||
// Transfer tokens.
|
// Transfer tokens.
|
||||||
// We do a raw call so we can check the success separate
|
// We do a raw call so we can check the success separate
|
||||||
// from the return data.
|
// from the return data.
|
||||||
bool success = token.call(abi.encodeWithSelector(
|
// We construct calldata for the `token.transferFrom` ABI.
|
||||||
IERC20Token(token).transferFrom.selector,
|
// The layout of this calldata is in the table below.
|
||||||
from,
|
//
|
||||||
to,
|
// | Area | Offset | Length | Contents |
|
||||||
amount
|
// |----------|--------|---------|-------------------------------------|
|
||||||
));
|
// | Header | 0 | 4 | function selector |
|
||||||
require(
|
// | Params | | 3 * 32 | function parameters: |
|
||||||
success,
|
// | | 4 | | 1. from |
|
||||||
TRANSFER_FAILED
|
// | | 36 | | 2. to |
|
||||||
);
|
// | | 68 | | 3. amount |
|
||||||
|
|
||||||
// Check return data.
|
bytes4 transferFromSelector = IERC20Token(token).transferFrom.selector;
|
||||||
// If there is no return data, we assume the token incorrectly
|
bool success;
|
||||||
// does not return a bool. In this case we expect it to revert
|
|
||||||
// on failure, which was handled above.
|
|
||||||
// If the token does return data, we require that it is a single
|
|
||||||
// value that evaluates to true.
|
|
||||||
assembly {
|
assembly {
|
||||||
if returndatasize {
|
/////// Setup State ///////
|
||||||
success := 0
|
// `cdStart` is the start of the calldata for `token.transferFrom` (equal to free memory ptr).
|
||||||
if eq(returndatasize, 32) {
|
let cdStart := mload(64)
|
||||||
// First 64 bytes of memory are reserved scratch space
|
|
||||||
returndatacopy(0, 0, 32)
|
/////// Setup Header Area ///////
|
||||||
success := mload(0)
|
// This area holds the 4-byte `transferFromSelector`.
|
||||||
}
|
// Any trailing data in transferFromSelector will be
|
||||||
}
|
// overwritten in the next `mstore` call.
|
||||||
|
mstore(cdStart, transferFromSelector)
|
||||||
|
|
||||||
|
/////// Setup Params Area ///////
|
||||||
|
// Each parameter is padded to 32-bytes.
|
||||||
|
// The entire Params Area is 96 bytes.
|
||||||
|
// A 20-byte mask is applied to addresses to
|
||||||
|
// zero-out the unused bytes.
|
||||||
|
mstore(add(cdStart, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff))
|
||||||
|
mstore(add(cdStart, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff))
|
||||||
|
mstore(add(cdStart, 68), amount)
|
||||||
|
|
||||||
|
/////// Call `token.transferFrom` using the calldata ///////
|
||||||
|
success := call(
|
||||||
|
gas, // forward all gas
|
||||||
|
token, // call address of token contract
|
||||||
|
0, // don't send any ETH
|
||||||
|
cdStart, // pointer to start of input
|
||||||
|
100, // length of input
|
||||||
|
cdStart, // write output over input
|
||||||
|
32 // output size should be 32 bytes
|
||||||
|
)
|
||||||
|
|
||||||
|
/////// Check return data. ///////
|
||||||
|
// If there is no return data, we assume the token incorrectly
|
||||||
|
// does not return a bool. In this case we expect it to revert
|
||||||
|
// on failure, which was handled above.
|
||||||
|
// If the token does return data, we require that it is a single
|
||||||
|
// nonzero 32 bytes value.
|
||||||
|
// So the transfer succeeded if the call succeeded and either
|
||||||
|
// returned nothing, or returned a non-zero 32 byte value.
|
||||||
|
success := and(success, or(
|
||||||
|
iszero(returndatasize),
|
||||||
|
and(
|
||||||
|
eq(returndatasize, 32),
|
||||||
|
gt(mload(cdStart), 0)
|
||||||
|
)
|
||||||
|
))
|
||||||
}
|
}
|
||||||
require(
|
require(
|
||||||
success,
|
success,
|
||||||
TRANSFER_FAILED
|
"TRANSFER_FAILED"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,6 +28,8 @@ contract MixinERC721Transfer is
|
|||||||
{
|
{
|
||||||
using LibBytes for bytes;
|
using LibBytes for bytes;
|
||||||
|
|
||||||
|
bytes4 constant SAFE_TRANSFER_FROM_SELECTOR = bytes4(keccak256("safeTransferFrom(address,address,uint256,bytes)"));
|
||||||
|
|
||||||
/// @dev Internal version of `transferFrom`.
|
/// @dev Internal version of `transferFrom`.
|
||||||
/// @param assetData Encoded byte array.
|
/// @param assetData Encoded byte array.
|
||||||
/// @param from Address to transfer asset from.
|
/// @param from Address to transfer asset from.
|
||||||
@@ -54,11 +56,85 @@ contract MixinERC721Transfer is
|
|||||||
bytes memory receiverData
|
bytes memory receiverData
|
||||||
) = decodeERC721AssetData(assetData);
|
) = decodeERC721AssetData(assetData);
|
||||||
|
|
||||||
ERC721Token(token).safeTransferFrom(
|
// We construct calldata for the `token.safeTransferFrom` ABI.
|
||||||
from,
|
// The layout of this calldata is in the table below.
|
||||||
to,
|
//
|
||||||
tokenId,
|
// | Area | Offset | Length | Contents |
|
||||||
receiverData
|
// |----------|--------|---------|-------------------------------------|
|
||||||
|
// | Header | 0 | 4 | function selector |
|
||||||
|
// | Params | | 4 * 32 | function parameters: |
|
||||||
|
// | | 4 | | 1. from |
|
||||||
|
// | | 36 | | 2. to |
|
||||||
|
// | | 68 | | 3. tokenId |
|
||||||
|
// | | 100 | | 4. offset to receiverData (*) |
|
||||||
|
// | Data | | | receiverData: |
|
||||||
|
// | | 132 | 32 | receiverData Length |
|
||||||
|
// | | 164 | ** | receiverData Contents |
|
||||||
|
|
||||||
|
bytes4 safeTransferFromSelector = SAFE_TRANSFER_FROM_SELECTOR;
|
||||||
|
bool success;
|
||||||
|
assembly {
|
||||||
|
/////// Setup State ///////
|
||||||
|
// `cdStart` is the start of the calldata for
|
||||||
|
// `token.safeTransferFrom` (equal to free memory ptr).
|
||||||
|
let cdStart := mload(64)
|
||||||
|
// `dataAreaLength` is the total number of words
|
||||||
|
// needed to store `receiverData`
|
||||||
|
// As-per the ABI spec, this value is padded up to
|
||||||
|
// the nearest multiple of 32,
|
||||||
|
// and includes 32-bytes for length.
|
||||||
|
// It's calculated as folows:
|
||||||
|
// - Unpadded length in bytes = `mload(receiverData) + 32`
|
||||||
|
// - Add 31 to convert rounding down to rounding up.
|
||||||
|
// Combined with the previous and this is `63`.
|
||||||
|
// - Round down to nearest multiple of 32 by clearing
|
||||||
|
// bits 0x1F. This is done with `and` and a mask.
|
||||||
|
let dataAreaLength := and(add(mload(receiverData), 63), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0)
|
||||||
|
// `cdEnd` is the end of the calldata for `token.safeTransferFrom`.
|
||||||
|
let cdEnd := add(cdStart, add(132, dataAreaLength))
|
||||||
|
|
||||||
|
/////// Setup Header Area ///////
|
||||||
|
// This area holds the 4-byte `transferFromSelector`.
|
||||||
|
// Any trailing data in transferFromSelector will be
|
||||||
|
// overwritten in the next `mstore` call.
|
||||||
|
mstore(cdStart, safeTransferFromSelector)
|
||||||
|
|
||||||
|
/////// Setup Params Area ///////
|
||||||
|
// Each parameter is padded to 32-bytes.
|
||||||
|
// The entire Params Area is 128 bytes.
|
||||||
|
// Notes:
|
||||||
|
// 1. A 20-byte mask is applied to addresses
|
||||||
|
// to zero-out the unused bytes.
|
||||||
|
// 2. The offset to `receiverData` is the length
|
||||||
|
// of the Params Area (128 bytes).
|
||||||
|
mstore(add(cdStart, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff))
|
||||||
|
mstore(add(cdStart, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff))
|
||||||
|
mstore(add(cdStart, 68), tokenId)
|
||||||
|
mstore(add(cdStart, 100), 128)
|
||||||
|
|
||||||
|
/////// Setup Data Area ///////
|
||||||
|
// This area holds `receiverData`.
|
||||||
|
let dataArea := add(cdStart, 132)
|
||||||
|
for {} lt(dataArea, cdEnd) {} {
|
||||||
|
mstore(dataArea, mload(receiverData))
|
||||||
|
dataArea := add(dataArea, 32)
|
||||||
|
receiverData := add(receiverData, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
/////// Call `token.safeTransferFrom` using the calldata ///////
|
||||||
|
success := call(
|
||||||
|
gas, // forward all gas
|
||||||
|
token, // call address of token contract
|
||||||
|
0, // don't send any ETH
|
||||||
|
cdStart, // pointer to start of input
|
||||||
|
sub(cdEnd, cdStart), // length of input
|
||||||
|
cdStart, // write output over input
|
||||||
|
0 // output size is 0 bytes
|
||||||
|
)
|
||||||
|
}
|
||||||
|
require(
|
||||||
|
success,
|
||||||
|
TRANSFER_FAILED
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,11 +155,9 @@ contract MixinERC721Transfer is
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
// Decode asset data.
|
// Decode asset data.
|
||||||
token = assetData.readAddress(0);
|
token = assetData.readAddress(16);
|
||||||
tokenId = assetData.readUint256(20);
|
tokenId = assetData.readUint256(36);
|
||||||
if (assetData.length > 52) {
|
receiverData = assetData.readBytesWithLength(100);
|
||||||
receiverData = assetData.readBytesWithLength(52);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
token,
|
token,
|
||||||
|
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Copyright 2018 ZeroEx Intl.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
pragma solidity ^0.4.23;
|
||||||
|
|
||||||
|
// @dev Interface of the asset proxy's assetData.
|
||||||
|
// The asset proxies take an ABI encoded `bytes assetData` as argument.
|
||||||
|
// This argument is ABI encoded as one of the methods of this interface.
|
||||||
|
interface IAssetData {
|
||||||
|
|
||||||
|
function ERC20Token(
|
||||||
|
address tokenContract)
|
||||||
|
external pure;
|
||||||
|
|
||||||
|
function ERC721Token(
|
||||||
|
address tokenContract,
|
||||||
|
uint256 tokenId,
|
||||||
|
bytes receiverData)
|
||||||
|
external pure;
|
||||||
|
|
||||||
|
}
|
@@ -56,6 +56,5 @@ contract IAssetProxy is
|
|||||||
function getProxyId()
|
function getProxyId()
|
||||||
external
|
external
|
||||||
view
|
view
|
||||||
returns (uint8);
|
returns (bytes4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,7 +18,10 @@
|
|||||||
|
|
||||||
pragma solidity ^0.4.24;
|
pragma solidity ^0.4.24;
|
||||||
|
|
||||||
|
/// @dev This contract documents the revert reasons used in the AssetProxy contracts.
|
||||||
|
/// This contract is intended to serve as a reference, but is not actually used for efficiency reasons.
|
||||||
contract LibAssetProxyErrors {
|
contract LibAssetProxyErrors {
|
||||||
|
|
||||||
/// Authorizable errors ///
|
/// Authorizable errors ///
|
||||||
string constant SENDER_NOT_AUTHORIZED = "SENDER_NOT_AUTHORIZED"; // Sender not authorized to call this method.
|
string constant SENDER_NOT_AUTHORIZED = "SENDER_NOT_AUTHORIZED"; // Sender not authorized to call this method.
|
||||||
string constant TARGET_NOT_AUTHORIZED = "TARGET_NOT_AUTHORIZED"; // Target address not authorized to call this method.
|
string constant TARGET_NOT_AUTHORIZED = "TARGET_NOT_AUTHORIZED"; // Target address not authorized to call this method.
|
||||||
|
@@ -18,7 +18,10 @@
|
|||||||
|
|
||||||
pragma solidity ^0.4.24;
|
pragma solidity ^0.4.24;
|
||||||
|
|
||||||
|
/// @dev This contract documents the revert reasons used in the `transferFrom` methods of different AssetProxy contracts.
|
||||||
|
/// This contract is intended to serve as a reference, but is not actually used for efficiency reasons.
|
||||||
contract LibTransferErrors {
|
contract LibTransferErrors {
|
||||||
|
|
||||||
/// Transfer errors ///
|
/// Transfer errors ///
|
||||||
string constant INVALID_AMOUNT = "INVALID_AMOUNT"; // Transfer amount must equal 1.
|
string constant INVALID_AMOUNT = "INVALID_AMOUNT"; // Transfer amount must equal 1.
|
||||||
string constant TRANSFER_FAILED = "TRANSFER_FAILED"; // Transfer failed.
|
string constant TRANSFER_FAILED = "TRANSFER_FAILED"; // Transfer failed.
|
||||||
|
@@ -19,17 +19,18 @@
|
|||||||
pragma solidity ^0.4.24;
|
pragma solidity ^0.4.24;
|
||||||
|
|
||||||
import "../../utils/Ownable/Ownable.sol";
|
import "../../utils/Ownable/Ownable.sol";
|
||||||
import "./libs/LibExchangeErrors.sol";
|
import "../../utils/LibBytes/LibBytes.sol";
|
||||||
import "./mixins/MAssetProxyDispatcher.sol";
|
import "./mixins/MAssetProxyDispatcher.sol";
|
||||||
import "../AssetProxy/interfaces/IAssetProxy.sol";
|
import "../AssetProxy/interfaces/IAssetProxy.sol";
|
||||||
|
|
||||||
contract MixinAssetProxyDispatcher is
|
contract MixinAssetProxyDispatcher is
|
||||||
Ownable,
|
Ownable,
|
||||||
LibExchangeErrors,
|
|
||||||
MAssetProxyDispatcher
|
MAssetProxyDispatcher
|
||||||
{
|
{
|
||||||
|
using LibBytes for bytes;
|
||||||
|
|
||||||
// Mapping from Asset Proxy Id's to their respective Asset Proxy
|
// Mapping from Asset Proxy Id's to their respective Asset Proxy
|
||||||
mapping (uint8 => IAssetProxy) public assetProxies;
|
mapping (bytes4 => IAssetProxy) public assetProxies;
|
||||||
|
|
||||||
/// @dev Registers an asset proxy to an asset proxy id.
|
/// @dev Registers an asset proxy to an asset proxy id.
|
||||||
/// An id can only be assigned to a single proxy at a given time.
|
/// An id can only be assigned to a single proxy at a given time.
|
||||||
@@ -37,7 +38,7 @@ contract MixinAssetProxyDispatcher is
|
|||||||
/// @param newAssetProxy Address of new asset proxy to register, or 0x0 to unset assetProxyId.
|
/// @param newAssetProxy Address of new asset proxy to register, or 0x0 to unset assetProxyId.
|
||||||
/// @param oldAssetProxy Existing asset proxy to overwrite, or 0x0 if assetProxyId is currently unused.
|
/// @param oldAssetProxy Existing asset proxy to overwrite, or 0x0 if assetProxyId is currently unused.
|
||||||
function registerAssetProxy(
|
function registerAssetProxy(
|
||||||
uint8 assetProxyId,
|
bytes4 assetProxyId,
|
||||||
address newAssetProxy,
|
address newAssetProxy,
|
||||||
address oldAssetProxy
|
address oldAssetProxy
|
||||||
)
|
)
|
||||||
@@ -48,17 +49,17 @@ contract MixinAssetProxyDispatcher is
|
|||||||
address currentAssetProxy = assetProxies[assetProxyId];
|
address currentAssetProxy = assetProxies[assetProxyId];
|
||||||
require(
|
require(
|
||||||
oldAssetProxy == currentAssetProxy,
|
oldAssetProxy == currentAssetProxy,
|
||||||
ASSET_PROXY_MISMATCH
|
"ASSET_PROXY_MISMATCH"
|
||||||
);
|
);
|
||||||
|
|
||||||
IAssetProxy assetProxy = IAssetProxy(newAssetProxy);
|
IAssetProxy assetProxy = IAssetProxy(newAssetProxy);
|
||||||
|
|
||||||
// Ensure that the id of newAssetProxy matches the passed in assetProxyId, unless it is being reset to 0.
|
// Ensure that the id of newAssetProxy matches the passed in assetProxyId, unless it is being reset to 0.
|
||||||
if (newAssetProxy != address(0)) {
|
if (newAssetProxy != address(0)) {
|
||||||
uint8 newAssetProxyId = assetProxy.getProxyId();
|
bytes4 newAssetProxyId = assetProxy.getProxyId();
|
||||||
require(
|
require(
|
||||||
newAssetProxyId == assetProxyId,
|
newAssetProxyId == assetProxyId,
|
||||||
ASSET_PROXY_ID_MISMATCH
|
"ASSET_PROXY_ID_MISMATCH"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ contract MixinAssetProxyDispatcher is
|
|||||||
/// @dev Gets an asset proxy.
|
/// @dev Gets an asset proxy.
|
||||||
/// @param assetProxyId Id of the asset proxy.
|
/// @param assetProxyId Id of the asset proxy.
|
||||||
/// @return The asset proxy registered to assetProxyId. Returns 0x0 if no proxy is registered.
|
/// @return The asset proxy registered to assetProxyId. Returns 0x0 if no proxy is registered.
|
||||||
function getAssetProxy(uint8 assetProxyId)
|
function getAssetProxy(bytes4 assetProxyId)
|
||||||
external
|
external
|
||||||
view
|
view
|
||||||
returns (address)
|
returns (address)
|
||||||
@@ -83,14 +84,12 @@ contract MixinAssetProxyDispatcher is
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws.
|
/// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws.
|
||||||
/// @param assetData Byte array encoded for the respective asset proxy.
|
/// @param assetData Byte array encoded for the asset.
|
||||||
/// @param assetProxyId Id of assetProxy to dispach to.
|
|
||||||
/// @param from Address to transfer token from.
|
/// @param from Address to transfer token from.
|
||||||
/// @param to Address to transfer token to.
|
/// @param to Address to transfer token to.
|
||||||
/// @param amount Amount of token to transfer.
|
/// @param amount Amount of token to transfer.
|
||||||
function dispatchTransferFrom(
|
function dispatchTransferFrom(
|
||||||
bytes memory assetData,
|
bytes memory assetData,
|
||||||
uint8 assetProxyId,
|
|
||||||
address from,
|
address from,
|
||||||
address to,
|
address to,
|
||||||
uint256 amount
|
uint256 amount
|
||||||
@@ -99,19 +98,94 @@ contract MixinAssetProxyDispatcher is
|
|||||||
{
|
{
|
||||||
// Do nothing if no amount should be transferred.
|
// Do nothing if no amount should be transferred.
|
||||||
if (amount > 0) {
|
if (amount > 0) {
|
||||||
|
// Ensure assetData length is valid
|
||||||
|
require(
|
||||||
|
assetData.length > 3,
|
||||||
|
"LENGTH_GREATER_THAN_3_REQUIRED"
|
||||||
|
);
|
||||||
|
|
||||||
// Lookup assetProxy
|
// Lookup assetProxy
|
||||||
|
bytes4 assetProxyId;
|
||||||
|
assembly {
|
||||||
|
assetProxyId := and(mload(
|
||||||
|
add(assetData, 32)),
|
||||||
|
0xFFFFFFFF00000000000000000000000000000000000000000000000000000000
|
||||||
|
)
|
||||||
|
}
|
||||||
IAssetProxy assetProxy = assetProxies[assetProxyId];
|
IAssetProxy assetProxy = assetProxies[assetProxyId];
|
||||||
|
|
||||||
// Ensure that assetProxy exists
|
// Ensure that assetProxy exists
|
||||||
require(
|
require(
|
||||||
assetProxy != address(0),
|
assetProxy != address(0),
|
||||||
ASSET_PROXY_DOES_NOT_EXIST
|
"ASSET_PROXY_DOES_NOT_EXIST"
|
||||||
);
|
);
|
||||||
// transferFrom will either succeed or throw.
|
|
||||||
assetProxy.transferFrom(
|
// We construct calldata for the `assetProxy.transferFrom` ABI.
|
||||||
assetData,
|
// The layout of this calldata is in the table below.
|
||||||
from,
|
//
|
||||||
to,
|
// | Area | Offset | Length | Contents |
|
||||||
amount
|
// | -------- |--------|---------|-------------------------------------------- |
|
||||||
|
// | Header | 0 | 4 | function selector |
|
||||||
|
// | Params | | 4 * 32 | function parameters: |
|
||||||
|
// | | 4 | | 1. offset to assetData (*) |
|
||||||
|
// | | 36 | | 2. from |
|
||||||
|
// | | 68 | | 3. to |
|
||||||
|
// | | 100 | | 4. amount |
|
||||||
|
// | Data | | | assetData: |
|
||||||
|
// | | 132 | 32 | assetData Length |
|
||||||
|
// | | 164 | ** | assetData Contents |
|
||||||
|
|
||||||
|
bytes4 transferFromSelector = IAssetProxy(assetProxy).transferFrom.selector;
|
||||||
|
bool success;
|
||||||
|
assembly {
|
||||||
|
/////// Setup State ///////
|
||||||
|
// `cdStart` is the start of the calldata for `assetProxy.transferFrom` (equal to free memory ptr).
|
||||||
|
let cdStart := mload(64)
|
||||||
|
// `dataAreaLength` is the total number of words needed to store `assetData`
|
||||||
|
// As-per the ABI spec, this value is padded up to the nearest multiple of 32,
|
||||||
|
// and includes 32-bytes for length.
|
||||||
|
let dataAreaLength := and(add(mload(assetData), 63), 0xFFFFFFFFFFFE0)
|
||||||
|
// `cdEnd` is the end of the calldata for `assetProxy.transferFrom`.
|
||||||
|
let cdEnd := add(cdStart, add(132, dataAreaLength))
|
||||||
|
|
||||||
|
|
||||||
|
/////// Setup Header Area ///////
|
||||||
|
// This area holds the 4-byte `transferFromSelector`.
|
||||||
|
mstore(cdStart, transferFromSelector)
|
||||||
|
|
||||||
|
/////// Setup Params Area ///////
|
||||||
|
// Each parameter is padded to 32-bytes. The entire Params Area is 128 bytes.
|
||||||
|
// Notes:
|
||||||
|
// 1. The offset to `assetData` is the length of the Params Area (128 bytes).
|
||||||
|
// 2. A 20-byte mask is applied to addresses to zero-out the unused bytes.
|
||||||
|
mstore(add(cdStart, 4), 128)
|
||||||
|
mstore(add(cdStart, 36), and(from, 0xffffffffffffffffffffffffffffffffffffffff))
|
||||||
|
mstore(add(cdStart, 68), and(to, 0xffffffffffffffffffffffffffffffffffffffff))
|
||||||
|
mstore(add(cdStart, 100), amount)
|
||||||
|
|
||||||
|
/////// Setup Data Area ///////
|
||||||
|
// This area holds `assetData`.
|
||||||
|
let dataArea := add(cdStart, 132)
|
||||||
|
for {} lt(dataArea, cdEnd) {} {
|
||||||
|
mstore(dataArea, mload(assetData))
|
||||||
|
dataArea := add(dataArea, 32)
|
||||||
|
assetData := add(assetData, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
/////// Call `assetProxy.transferFrom` using the constructed calldata ///////
|
||||||
|
success := call(
|
||||||
|
gas, // forward all gas
|
||||||
|
assetProxy, // call address of asset proxy
|
||||||
|
0, // don't send any ETH
|
||||||
|
cdStart, // pointer to start of input
|
||||||
|
sub(cdEnd, cdStart), // length of input
|
||||||
|
cdStart, // write output over input
|
||||||
|
0 // output size is 0 bytes
|
||||||
|
)
|
||||||
|
}
|
||||||
|
require(
|
||||||
|
success,
|
||||||
|
"TRANSFER_FAILED"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,11 +20,9 @@ pragma solidity ^0.4.24;
|
|||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
import "./libs/LibConstants.sol";
|
import "./libs/LibConstants.sol";
|
||||||
import "../../utils/LibBytes/LibBytes.sol";
|
|
||||||
import "./libs/LibFillResults.sol";
|
import "./libs/LibFillResults.sol";
|
||||||
import "./libs/LibOrder.sol";
|
import "./libs/LibOrder.sol";
|
||||||
import "./libs/LibMath.sol";
|
import "./libs/LibMath.sol";
|
||||||
import "./libs/LibExchangeErrors.sol";
|
|
||||||
import "./mixins/MExchangeCore.sol";
|
import "./mixins/MExchangeCore.sol";
|
||||||
import "./mixins/MSignatureValidator.sol";
|
import "./mixins/MSignatureValidator.sol";
|
||||||
import "./mixins/MTransactions.sol";
|
import "./mixins/MTransactions.sol";
|
||||||
@@ -35,14 +33,11 @@ contract MixinExchangeCore is
|
|||||||
LibMath,
|
LibMath,
|
||||||
LibOrder,
|
LibOrder,
|
||||||
LibFillResults,
|
LibFillResults,
|
||||||
LibExchangeErrors,
|
|
||||||
MAssetProxyDispatcher,
|
MAssetProxyDispatcher,
|
||||||
MExchangeCore,
|
MExchangeCore,
|
||||||
MSignatureValidator,
|
MSignatureValidator,
|
||||||
MTransactions
|
MTransactions
|
||||||
{
|
{
|
||||||
using LibBytes for bytes;
|
|
||||||
|
|
||||||
// Mapping of orderHash => amount of takerAsset already bought by maker
|
// Mapping of orderHash => amount of takerAsset already bought by maker
|
||||||
mapping (bytes32 => uint256) public filled;
|
mapping (bytes32 => uint256) public filled;
|
||||||
|
|
||||||
@@ -73,7 +68,7 @@ contract MixinExchangeCore is
|
|||||||
// Ensure orderEpoch is monotonically increasing
|
// Ensure orderEpoch is monotonically increasing
|
||||||
require(
|
require(
|
||||||
newOrderEpoch > oldOrderEpoch,
|
newOrderEpoch > oldOrderEpoch,
|
||||||
INVALID_NEW_ORDER_EPOCH
|
"INVALID_NEW_ORDER_EPOCH"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update orderEpoch
|
// Update orderEpoch
|
||||||
@@ -285,20 +280,20 @@ contract MixinExchangeCore is
|
|||||||
// An order can only be filled if its status is FILLABLE.
|
// An order can only be filled if its status is FILLABLE.
|
||||||
require(
|
require(
|
||||||
orderInfo.orderStatus == uint8(OrderStatus.FILLABLE),
|
orderInfo.orderStatus == uint8(OrderStatus.FILLABLE),
|
||||||
ORDER_UNFILLABLE
|
"ORDER_UNFILLABLE"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Revert if fill amount is invalid
|
// Revert if fill amount is invalid
|
||||||
require(
|
require(
|
||||||
takerAssetFillAmount != 0,
|
takerAssetFillAmount != 0,
|
||||||
INVALID_TAKER_AMOUNT
|
"INVALID_TAKER_AMOUNT"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Validate sender is allowed to fill this order
|
// Validate sender is allowed to fill this order
|
||||||
if (order.senderAddress != address(0)) {
|
if (order.senderAddress != address(0)) {
|
||||||
require(
|
require(
|
||||||
order.senderAddress == msg.sender,
|
order.senderAddress == msg.sender,
|
||||||
INVALID_SENDER
|
"INVALID_SENDER"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +301,7 @@ contract MixinExchangeCore is
|
|||||||
if (order.takerAddress != address(0)) {
|
if (order.takerAddress != address(0)) {
|
||||||
require(
|
require(
|
||||||
order.takerAddress == takerAddress,
|
order.takerAddress == takerAddress,
|
||||||
INVALID_TAKER
|
"INVALID_TAKER"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,7 +313,7 @@ contract MixinExchangeCore is
|
|||||||
order.makerAddress,
|
order.makerAddress,
|
||||||
signature
|
signature
|
||||||
),
|
),
|
||||||
INVALID_ORDER_SIGNATURE
|
"INVALID_ORDER_SIGNATURE"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,7 +324,7 @@ contract MixinExchangeCore is
|
|||||||
order.takerAssetAmount,
|
order.takerAssetAmount,
|
||||||
order.makerAssetAmount
|
order.makerAssetAmount
|
||||||
),
|
),
|
||||||
ROUNDING_ERROR
|
"ROUNDING_ERROR"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,14 +342,14 @@ contract MixinExchangeCore is
|
|||||||
// An order can only be cancelled if its status is FILLABLE.
|
// An order can only be cancelled if its status is FILLABLE.
|
||||||
require(
|
require(
|
||||||
orderInfo.orderStatus == uint8(OrderStatus.FILLABLE),
|
orderInfo.orderStatus == uint8(OrderStatus.FILLABLE),
|
||||||
ORDER_UNFILLABLE
|
"ORDER_UNFILLABLE"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Validate sender is allowed to cancel this order
|
// Validate sender is allowed to cancel this order
|
||||||
if (order.senderAddress != address(0)) {
|
if (order.senderAddress != address(0)) {
|
||||||
require(
|
require(
|
||||||
order.senderAddress == msg.sender,
|
order.senderAddress == msg.sender,
|
||||||
INVALID_SENDER
|
"INVALID_SENDER"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,7 +357,7 @@ contract MixinExchangeCore is
|
|||||||
address makerAddress = getCurrentContextAddress();
|
address makerAddress = getCurrentContextAddress();
|
||||||
require(
|
require(
|
||||||
order.makerAddress == makerAddress,
|
order.makerAddress == makerAddress,
|
||||||
INVALID_MAKER
|
"INVALID_MAKER"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,33 +407,27 @@ contract MixinExchangeCore is
|
|||||||
)
|
)
|
||||||
private
|
private
|
||||||
{
|
{
|
||||||
uint8 makerAssetProxyId = uint8(order.makerAssetData.popLastByte());
|
|
||||||
uint8 takerAssetProxyId = uint8(order.takerAssetData.popLastByte());
|
|
||||||
bytes memory zrxAssetData = ZRX_ASSET_DATA;
|
bytes memory zrxAssetData = ZRX_ASSET_DATA;
|
||||||
dispatchTransferFrom(
|
dispatchTransferFrom(
|
||||||
order.makerAssetData,
|
order.makerAssetData,
|
||||||
makerAssetProxyId,
|
|
||||||
order.makerAddress,
|
order.makerAddress,
|
||||||
takerAddress,
|
takerAddress,
|
||||||
fillResults.makerAssetFilledAmount
|
fillResults.makerAssetFilledAmount
|
||||||
);
|
);
|
||||||
dispatchTransferFrom(
|
dispatchTransferFrom(
|
||||||
order.takerAssetData,
|
order.takerAssetData,
|
||||||
takerAssetProxyId,
|
|
||||||
takerAddress,
|
takerAddress,
|
||||||
order.makerAddress,
|
order.makerAddress,
|
||||||
fillResults.takerAssetFilledAmount
|
fillResults.takerAssetFilledAmount
|
||||||
);
|
);
|
||||||
dispatchTransferFrom(
|
dispatchTransferFrom(
|
||||||
zrxAssetData,
|
zrxAssetData,
|
||||||
ZRX_PROXY_ID,
|
|
||||||
order.makerAddress,
|
order.makerAddress,
|
||||||
order.feeRecipientAddress,
|
order.feeRecipientAddress,
|
||||||
fillResults.makerFeePaid
|
fillResults.makerFeePaid
|
||||||
);
|
);
|
||||||
dispatchTransferFrom(
|
dispatchTransferFrom(
|
||||||
zrxAssetData,
|
zrxAssetData,
|
||||||
ZRX_PROXY_ID,
|
|
||||||
takerAddress,
|
takerAddress,
|
||||||
order.feeRecipientAddress,
|
order.feeRecipientAddress,
|
||||||
fillResults.takerFeePaid
|
fillResults.takerFeePaid
|
||||||
|
@@ -15,11 +15,9 @@ pragma solidity ^0.4.24;
|
|||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
import "./libs/LibConstants.sol";
|
import "./libs/LibConstants.sol";
|
||||||
import "../../utils/LibBytes/LibBytes.sol";
|
|
||||||
import "./libs/LibMath.sol";
|
import "./libs/LibMath.sol";
|
||||||
import "./libs/LibOrder.sol";
|
import "./libs/LibOrder.sol";
|
||||||
import "./libs/LibFillResults.sol";
|
import "./libs/LibFillResults.sol";
|
||||||
import "./libs/LibExchangeErrors.sol";
|
|
||||||
import "./mixins/MExchangeCore.sol";
|
import "./mixins/MExchangeCore.sol";
|
||||||
import "./mixins/MMatchOrders.sol";
|
import "./mixins/MMatchOrders.sol";
|
||||||
import "./mixins/MTransactions.sol";
|
import "./mixins/MTransactions.sol";
|
||||||
@@ -28,14 +26,11 @@ import "./mixins/MAssetProxyDispatcher.sol";
|
|||||||
contract MixinMatchOrders is
|
contract MixinMatchOrders is
|
||||||
LibConstants,
|
LibConstants,
|
||||||
LibMath,
|
LibMath,
|
||||||
LibExchangeErrors,
|
|
||||||
MAssetProxyDispatcher,
|
MAssetProxyDispatcher,
|
||||||
MExchangeCore,
|
MExchangeCore,
|
||||||
MMatchOrders,
|
MMatchOrders,
|
||||||
MTransactions
|
MTransactions
|
||||||
{
|
{
|
||||||
using LibBytes for bytes;
|
|
||||||
|
|
||||||
/// @dev Match two complementary orders that have a profitable spread.
|
/// @dev Match two complementary orders that have a profitable spread.
|
||||||
/// Each order is filled at their respective price point. However, the calculations are
|
/// Each order is filled at their respective price point. However, the calculations are
|
||||||
/// carried out as though the orders are both being filled at the right order's price point.
|
/// carried out as though the orders are both being filled at the right order's price point.
|
||||||
@@ -144,7 +139,7 @@ contract MixinMatchOrders is
|
|||||||
require(
|
require(
|
||||||
safeMul(leftOrder.makerAssetAmount, rightOrder.makerAssetAmount) >=
|
safeMul(leftOrder.makerAssetAmount, rightOrder.makerAssetAmount) >=
|
||||||
safeMul(leftOrder.takerAssetAmount, rightOrder.takerAssetAmount),
|
safeMul(leftOrder.takerAssetAmount, rightOrder.takerAssetAmount),
|
||||||
NEGATIVE_SPREAD_REQUIRED
|
"NEGATIVE_SPREAD_REQUIRED"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,27 +237,22 @@ contract MixinMatchOrders is
|
|||||||
)
|
)
|
||||||
private
|
private
|
||||||
{
|
{
|
||||||
uint8 leftMakerAssetProxyId = uint8(leftOrder.makerAssetData.popLastByte());
|
|
||||||
uint8 rightMakerAssetProxyId = uint8(rightOrder.makerAssetData.popLastByte());
|
|
||||||
bytes memory zrxAssetData = ZRX_ASSET_DATA;
|
bytes memory zrxAssetData = ZRX_ASSET_DATA;
|
||||||
// Order makers and taker
|
// Order makers and taker
|
||||||
dispatchTransferFrom(
|
dispatchTransferFrom(
|
||||||
leftOrder.makerAssetData,
|
leftOrder.makerAssetData,
|
||||||
leftMakerAssetProxyId,
|
|
||||||
leftOrder.makerAddress,
|
leftOrder.makerAddress,
|
||||||
rightOrder.makerAddress,
|
rightOrder.makerAddress,
|
||||||
matchedFillResults.right.takerAssetFilledAmount
|
matchedFillResults.right.takerAssetFilledAmount
|
||||||
);
|
);
|
||||||
dispatchTransferFrom(
|
dispatchTransferFrom(
|
||||||
rightOrder.makerAssetData,
|
rightOrder.makerAssetData,
|
||||||
rightMakerAssetProxyId,
|
|
||||||
rightOrder.makerAddress,
|
rightOrder.makerAddress,
|
||||||
leftOrder.makerAddress,
|
leftOrder.makerAddress,
|
||||||
matchedFillResults.left.takerAssetFilledAmount
|
matchedFillResults.left.takerAssetFilledAmount
|
||||||
);
|
);
|
||||||
dispatchTransferFrom(
|
dispatchTransferFrom(
|
||||||
leftOrder.makerAssetData,
|
leftOrder.makerAssetData,
|
||||||
leftMakerAssetProxyId,
|
|
||||||
leftOrder.makerAddress,
|
leftOrder.makerAddress,
|
||||||
takerAddress,
|
takerAddress,
|
||||||
matchedFillResults.leftMakerAssetSpreadAmount
|
matchedFillResults.leftMakerAssetSpreadAmount
|
||||||
@@ -271,14 +261,12 @@ contract MixinMatchOrders is
|
|||||||
// Maker fees
|
// Maker fees
|
||||||
dispatchTransferFrom(
|
dispatchTransferFrom(
|
||||||
zrxAssetData,
|
zrxAssetData,
|
||||||
ZRX_PROXY_ID,
|
|
||||||
leftOrder.makerAddress,
|
leftOrder.makerAddress,
|
||||||
leftOrder.feeRecipientAddress,
|
leftOrder.feeRecipientAddress,
|
||||||
matchedFillResults.left.makerFeePaid
|
matchedFillResults.left.makerFeePaid
|
||||||
);
|
);
|
||||||
dispatchTransferFrom(
|
dispatchTransferFrom(
|
||||||
zrxAssetData,
|
zrxAssetData,
|
||||||
ZRX_PROXY_ID,
|
|
||||||
rightOrder.makerAddress,
|
rightOrder.makerAddress,
|
||||||
rightOrder.feeRecipientAddress,
|
rightOrder.feeRecipientAddress,
|
||||||
matchedFillResults.right.makerFeePaid
|
matchedFillResults.right.makerFeePaid
|
||||||
@@ -288,7 +276,6 @@ contract MixinMatchOrders is
|
|||||||
if (leftOrder.feeRecipientAddress == rightOrder.feeRecipientAddress) {
|
if (leftOrder.feeRecipientAddress == rightOrder.feeRecipientAddress) {
|
||||||
dispatchTransferFrom(
|
dispatchTransferFrom(
|
||||||
zrxAssetData,
|
zrxAssetData,
|
||||||
ZRX_PROXY_ID,
|
|
||||||
takerAddress,
|
takerAddress,
|
||||||
leftOrder.feeRecipientAddress,
|
leftOrder.feeRecipientAddress,
|
||||||
safeAdd(
|
safeAdd(
|
||||||
@@ -299,14 +286,12 @@ contract MixinMatchOrders is
|
|||||||
} else {
|
} else {
|
||||||
dispatchTransferFrom(
|
dispatchTransferFrom(
|
||||||
zrxAssetData,
|
zrxAssetData,
|
||||||
ZRX_PROXY_ID,
|
|
||||||
takerAddress,
|
takerAddress,
|
||||||
leftOrder.feeRecipientAddress,
|
leftOrder.feeRecipientAddress,
|
||||||
matchedFillResults.left.takerFeePaid
|
matchedFillResults.left.takerFeePaid
|
||||||
);
|
);
|
||||||
dispatchTransferFrom(
|
dispatchTransferFrom(
|
||||||
zrxAssetData,
|
zrxAssetData,
|
||||||
ZRX_PROXY_ID,
|
|
||||||
takerAddress,
|
takerAddress,
|
||||||
rightOrder.feeRecipientAddress,
|
rightOrder.feeRecipientAddress,
|
||||||
matchedFillResults.right.takerFeePaid
|
matchedFillResults.right.takerFeePaid
|
||||||
|
@@ -19,23 +19,17 @@
|
|||||||
pragma solidity ^0.4.24;
|
pragma solidity ^0.4.24;
|
||||||
|
|
||||||
import "../../utils/LibBytes/LibBytes.sol";
|
import "../../utils/LibBytes/LibBytes.sol";
|
||||||
import "./libs/LibExchangeErrors.sol";
|
|
||||||
import "./mixins/MSignatureValidator.sol";
|
import "./mixins/MSignatureValidator.sol";
|
||||||
import "./mixins/MTransactions.sol";
|
import "./mixins/MTransactions.sol";
|
||||||
import "./interfaces/IWallet.sol";
|
import "./interfaces/IWallet.sol";
|
||||||
import "./interfaces/IValidator.sol";
|
import "./interfaces/IValidator.sol";
|
||||||
|
|
||||||
contract MixinSignatureValidator is
|
contract MixinSignatureValidator is
|
||||||
LibExchangeErrors,
|
|
||||||
MSignatureValidator,
|
MSignatureValidator,
|
||||||
MTransactions
|
MTransactions
|
||||||
{
|
{
|
||||||
using LibBytes for bytes;
|
using LibBytes for bytes;
|
||||||
|
|
||||||
// Personal message headers
|
|
||||||
string constant ETH_PERSONAL_MESSAGE = "\x19Ethereum Signed Message:\n32";
|
|
||||||
string constant TREZOR_PERSONAL_MESSAGE = "\x19Ethereum Signed Message:\n\x20";
|
|
||||||
|
|
||||||
// Mapping of hash => signer => signed
|
// Mapping of hash => signer => signed
|
||||||
mapping (bytes32 => mapping (address => bool)) public preSigned;
|
mapping (bytes32 => mapping (address => bool)) public preSigned;
|
||||||
|
|
||||||
@@ -59,7 +53,7 @@ contract MixinSignatureValidator is
|
|||||||
signerAddress,
|
signerAddress,
|
||||||
signature
|
signature
|
||||||
),
|
),
|
||||||
INVALID_SIGNATURE
|
"INVALID_SIGNATURE"
|
||||||
);
|
);
|
||||||
preSigned[hash][signerAddress] = true;
|
preSigned[hash][signerAddress] = true;
|
||||||
}
|
}
|
||||||
@@ -99,14 +93,14 @@ contract MixinSignatureValidator is
|
|||||||
// TODO: Domain separation: make hash depend on role. (Taker sig should not be valid as maker sig, etc.)
|
// TODO: Domain separation: make hash depend on role. (Taker sig should not be valid as maker sig, etc.)
|
||||||
require(
|
require(
|
||||||
signature.length > 0,
|
signature.length > 0,
|
||||||
LENGTH_GREATER_THAN_0_REQUIRED
|
"LENGTH_GREATER_THAN_0_REQUIRED"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure signature is supported
|
// Ensure signature is supported
|
||||||
uint8 signatureTypeRaw = uint8(signature.popLastByte());
|
uint8 signatureTypeRaw = uint8(signature.popLastByte());
|
||||||
require(
|
require(
|
||||||
signatureTypeRaw < uint8(SignatureType.NSignatureTypes),
|
signatureTypeRaw < uint8(SignatureType.NSignatureTypes),
|
||||||
SIGNATURE_UNSUPPORTED
|
"SIGNATURE_UNSUPPORTED"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Pop last byte off of signature byte array.
|
// Pop last byte off of signature byte array.
|
||||||
@@ -124,7 +118,7 @@ contract MixinSignatureValidator is
|
|||||||
// it an explicit option. This aids testing and analysis. It is
|
// it an explicit option. This aids testing and analysis. It is
|
||||||
// also the initialization value for the enum type.
|
// also the initialization value for the enum type.
|
||||||
if (signatureType == SignatureType.Illegal) {
|
if (signatureType == SignatureType.Illegal) {
|
||||||
revert(SIGNATURE_ILLEGAL);
|
revert("SIGNATURE_ILLEGAL");
|
||||||
|
|
||||||
// Always invalid signature.
|
// Always invalid signature.
|
||||||
// Like Illegal, this is always implicitly available and therefore
|
// Like Illegal, this is always implicitly available and therefore
|
||||||
@@ -133,7 +127,7 @@ contract MixinSignatureValidator is
|
|||||||
} else if (signatureType == SignatureType.Invalid) {
|
} else if (signatureType == SignatureType.Invalid) {
|
||||||
require(
|
require(
|
||||||
signature.length == 0,
|
signature.length == 0,
|
||||||
LENGTH_0_REQUIRED
|
"LENGTH_0_REQUIRED"
|
||||||
);
|
);
|
||||||
isValid = false;
|
isValid = false;
|
||||||
return isValid;
|
return isValid;
|
||||||
@@ -142,7 +136,7 @@ contract MixinSignatureValidator is
|
|||||||
} else if (signatureType == SignatureType.EIP712) {
|
} else if (signatureType == SignatureType.EIP712) {
|
||||||
require(
|
require(
|
||||||
signature.length == 65,
|
signature.length == 65,
|
||||||
LENGTH_65_REQUIRED
|
"LENGTH_65_REQUIRED"
|
||||||
);
|
);
|
||||||
v = uint8(signature[0]);
|
v = uint8(signature[0]);
|
||||||
r = signature.readBytes32(1);
|
r = signature.readBytes32(1);
|
||||||
@@ -155,13 +149,16 @@ contract MixinSignatureValidator is
|
|||||||
} else if (signatureType == SignatureType.EthSign) {
|
} else if (signatureType == SignatureType.EthSign) {
|
||||||
require(
|
require(
|
||||||
signature.length == 65,
|
signature.length == 65,
|
||||||
LENGTH_65_REQUIRED
|
"LENGTH_65_REQUIRED"
|
||||||
);
|
);
|
||||||
v = uint8(signature[0]);
|
v = uint8(signature[0]);
|
||||||
r = signature.readBytes32(1);
|
r = signature.readBytes32(1);
|
||||||
s = signature.readBytes32(33);
|
s = signature.readBytes32(33);
|
||||||
recovered = ecrecover(
|
recovered = ecrecover(
|
||||||
keccak256(abi.encodePacked(ETH_PERSONAL_MESSAGE, hash)),
|
keccak256(abi.encodePacked(
|
||||||
|
"\x19Ethereum Signed Message:\n32",
|
||||||
|
hash
|
||||||
|
)),
|
||||||
v,
|
v,
|
||||||
r,
|
r,
|
||||||
s
|
s
|
||||||
@@ -180,7 +177,7 @@ contract MixinSignatureValidator is
|
|||||||
} else if (signatureType == SignatureType.Caller) {
|
} else if (signatureType == SignatureType.Caller) {
|
||||||
require(
|
require(
|
||||||
signature.length == 0,
|
signature.length == 0,
|
||||||
LENGTH_0_REQUIRED
|
"LENGTH_0_REQUIRED"
|
||||||
);
|
);
|
||||||
isValid = signerAddress == msg.sender;
|
isValid = signerAddress == msg.sender;
|
||||||
return isValid;
|
return isValid;
|
||||||
@@ -230,13 +227,16 @@ contract MixinSignatureValidator is
|
|||||||
} else if (signatureType == SignatureType.Trezor) {
|
} else if (signatureType == SignatureType.Trezor) {
|
||||||
require(
|
require(
|
||||||
signature.length == 65,
|
signature.length == 65,
|
||||||
LENGTH_65_REQUIRED
|
"LENGTH_65_REQUIRED"
|
||||||
);
|
);
|
||||||
v = uint8(signature[0]);
|
v = uint8(signature[0]);
|
||||||
r = signature.readBytes32(1);
|
r = signature.readBytes32(1);
|
||||||
s = signature.readBytes32(33);
|
s = signature.readBytes32(33);
|
||||||
recovered = ecrecover(
|
recovered = ecrecover(
|
||||||
keccak256(abi.encodePacked(TREZOR_PERSONAL_MESSAGE, hash)),
|
keccak256(abi.encodePacked(
|
||||||
|
"\x19Ethereum Signed Message:\n\x20",
|
||||||
|
hash
|
||||||
|
)),
|
||||||
v,
|
v,
|
||||||
r,
|
r,
|
||||||
s
|
s
|
||||||
@@ -250,6 +250,6 @@ contract MixinSignatureValidator is
|
|||||||
// that we currently support. In this case returning false
|
// that we currently support. In this case returning false
|
||||||
// may lead the caller to incorrectly believe that the
|
// may lead the caller to incorrectly believe that the
|
||||||
// signature was invalid.)
|
// signature was invalid.)
|
||||||
revert(SIGNATURE_UNSUPPORTED);
|
revert("SIGNATURE_UNSUPPORTED");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,12 +20,10 @@ pragma solidity ^0.4.24;
|
|||||||
import "./libs/LibExchangeErrors.sol";
|
import "./libs/LibExchangeErrors.sol";
|
||||||
import "./mixins/MSignatureValidator.sol";
|
import "./mixins/MSignatureValidator.sol";
|
||||||
import "./mixins/MTransactions.sol";
|
import "./mixins/MTransactions.sol";
|
||||||
import "./libs/LibExchangeErrors.sol";
|
|
||||||
import "./libs/LibEIP712.sol";
|
import "./libs/LibEIP712.sol";
|
||||||
|
|
||||||
contract MixinTransactions is
|
contract MixinTransactions is
|
||||||
LibEIP712,
|
LibEIP712,
|
||||||
LibExchangeErrors,
|
|
||||||
MSignatureValidator,
|
MSignatureValidator,
|
||||||
MTransactions
|
MTransactions
|
||||||
{
|
{
|
||||||
@@ -90,7 +88,7 @@ contract MixinTransactions is
|
|||||||
// Prevent reentrancy
|
// Prevent reentrancy
|
||||||
require(
|
require(
|
||||||
currentContextAddress == address(0),
|
currentContextAddress == address(0),
|
||||||
REENTRANCY_ILLEGAL
|
"REENTRANCY_ILLEGAL"
|
||||||
);
|
);
|
||||||
|
|
||||||
bytes32 transactionHash = hashEIP712Message(hashZeroExTransaction(
|
bytes32 transactionHash = hashEIP712Message(hashZeroExTransaction(
|
||||||
@@ -102,7 +100,7 @@ contract MixinTransactions is
|
|||||||
// Validate transaction has not been executed
|
// Validate transaction has not been executed
|
||||||
require(
|
require(
|
||||||
!transactions[transactionHash],
|
!transactions[transactionHash],
|
||||||
INVALID_TX_HASH
|
"INVALID_TX_HASH"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Transaction always valid if signer is sender of transaction
|
// Transaction always valid if signer is sender of transaction
|
||||||
@@ -114,7 +112,7 @@ contract MixinTransactions is
|
|||||||
signerAddress,
|
signerAddress,
|
||||||
signature
|
signature
|
||||||
),
|
),
|
||||||
INVALID_TX_SIGNATURE
|
"INVALID_TX_SIGNATURE"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Set the current transaction signer
|
// Set the current transaction signer
|
||||||
@@ -125,7 +123,7 @@ contract MixinTransactions is
|
|||||||
transactions[transactionHash] = true;
|
transactions[transactionHash] = true;
|
||||||
require(
|
require(
|
||||||
address(this).delegatecall(data),
|
address(this).delegatecall(data),
|
||||||
FAILED_EXECUTION
|
"FAILED_EXECUTION"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Reset current transaction signer
|
// Reset current transaction signer
|
||||||
|
@@ -22,13 +22,11 @@ pragma experimental ABIEncoderV2;
|
|||||||
import "./libs/LibMath.sol";
|
import "./libs/LibMath.sol";
|
||||||
import "./libs/LibOrder.sol";
|
import "./libs/LibOrder.sol";
|
||||||
import "./libs/LibFillResults.sol";
|
import "./libs/LibFillResults.sol";
|
||||||
import "./libs/LibExchangeErrors.sol";
|
|
||||||
import "./mixins/MExchangeCore.sol";
|
import "./mixins/MExchangeCore.sol";
|
||||||
|
|
||||||
contract MixinWrapperFunctions is
|
contract MixinWrapperFunctions is
|
||||||
LibMath,
|
LibMath,
|
||||||
LibFillResults,
|
LibFillResults,
|
||||||
LibExchangeErrors,
|
|
||||||
MExchangeCore
|
MExchangeCore
|
||||||
{
|
{
|
||||||
/// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled.
|
/// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled.
|
||||||
@@ -50,7 +48,7 @@ contract MixinWrapperFunctions is
|
|||||||
);
|
);
|
||||||
require(
|
require(
|
||||||
fillResults.takerAssetFilledAmount == takerAssetFillAmount,
|
fillResults.takerAssetFilledAmount == takerAssetFillAmount,
|
||||||
COMPLETE_FILL_FAILED
|
"COMPLETE_FILL_FAILED"
|
||||||
);
|
);
|
||||||
return fillResults;
|
return fillResults;
|
||||||
}
|
}
|
||||||
@@ -366,14 +364,6 @@ contract MixinWrapperFunctions is
|
|||||||
signatures[i]
|
signatures[i]
|
||||||
);
|
);
|
||||||
|
|
||||||
// HACK: the proxyId is "popped" from the byte array before a fill is settled
|
|
||||||
// by subtracting from the length of the array. Since the popped byte is
|
|
||||||
// still in memory, we can "unpop" it by incrementing the length of the byte array.
|
|
||||||
assembly {
|
|
||||||
let len := mload(takerAssetData)
|
|
||||||
mstore(takerAssetData, add(len, 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update amounts filled and fees paid by maker and taker
|
// Update amounts filled and fees paid by maker and taker
|
||||||
addFillResults(totalFillResults, singleFillResults);
|
addFillResults(totalFillResults, singleFillResults);
|
||||||
|
|
||||||
@@ -467,14 +457,6 @@ contract MixinWrapperFunctions is
|
|||||||
signatures[i]
|
signatures[i]
|
||||||
);
|
);
|
||||||
|
|
||||||
// HACK: the proxyId is "popped" from the byte array before a fill is settled
|
|
||||||
// by subtracting from the length of the array. Since the popped byte is
|
|
||||||
// still in memory, we can "unpop" it by incrementing the length of the byte array.
|
|
||||||
assembly {
|
|
||||||
let len := mload(makerAssetData)
|
|
||||||
mstore(makerAssetData, add(len, 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update amounts filled and fees paid by maker and taker
|
// Update amounts filled and fees paid by maker and taker
|
||||||
addFillResults(totalFillResults, singleFillResults);
|
addFillResults(totalFillResults, singleFillResults);
|
||||||
|
|
||||||
|
@@ -26,7 +26,7 @@ contract IAssetProxyDispatcher {
|
|||||||
/// @param newAssetProxy Address of new asset proxy to register, or 0x0 to unset assetProxyId.
|
/// @param newAssetProxy Address of new asset proxy to register, or 0x0 to unset assetProxyId.
|
||||||
/// @param oldAssetProxy Existing asset proxy to overwrite, or 0x0 if assetProxyId is currently unused.
|
/// @param oldAssetProxy Existing asset proxy to overwrite, or 0x0 if assetProxyId is currently unused.
|
||||||
function registerAssetProxy(
|
function registerAssetProxy(
|
||||||
uint8 assetProxyId,
|
bytes4 assetProxyId,
|
||||||
address newAssetProxy,
|
address newAssetProxy,
|
||||||
address oldAssetProxy
|
address oldAssetProxy
|
||||||
)
|
)
|
||||||
@@ -35,7 +35,7 @@ contract IAssetProxyDispatcher {
|
|||||||
/// @dev Gets an asset proxy.
|
/// @dev Gets an asset proxy.
|
||||||
/// @param assetProxyId Id of the asset proxy.
|
/// @param assetProxyId Id of the asset proxy.
|
||||||
/// @return The asset proxy registered to assetProxyId. Returns 0x0 if no proxy is registered.
|
/// @return The asset proxy registered to assetProxyId. Returns 0x0 if no proxy is registered.
|
||||||
function getAssetProxy(uint8 assetProxyId)
|
function getAssetProxy(bytes4 assetProxyId)
|
||||||
external
|
external
|
||||||
view
|
view
|
||||||
returns (address);
|
returns (address);
|
||||||
|
@@ -25,9 +25,6 @@ contract LibConstants {
|
|||||||
// not constant to make testing easier.
|
// not constant to make testing easier.
|
||||||
bytes public ZRX_ASSET_DATA;
|
bytes public ZRX_ASSET_DATA;
|
||||||
|
|
||||||
// Proxy Id for ZRX token.
|
|
||||||
uint8 constant ZRX_PROXY_ID = 1;
|
|
||||||
|
|
||||||
// @TODO: Remove when we deploy.
|
// @TODO: Remove when we deploy.
|
||||||
constructor (bytes memory zrxAssetData)
|
constructor (bytes memory zrxAssetData)
|
||||||
public
|
public
|
||||||
|
@@ -18,7 +18,10 @@
|
|||||||
|
|
||||||
pragma solidity ^0.4.24;
|
pragma solidity ^0.4.24;
|
||||||
|
|
||||||
|
/// @dev This contract documents the revert reasons used in the Exchange contract.
|
||||||
|
/// This contract is intended to serve as a reference, but is not actually used for efficiency reasons.
|
||||||
contract LibExchangeErrors {
|
contract LibExchangeErrors {
|
||||||
|
|
||||||
/// Order validation errors ///
|
/// Order validation errors ///
|
||||||
string constant ORDER_UNFILLABLE = "ORDER_UNFILLABLE"; // Order cannot be filled.
|
string constant ORDER_UNFILLABLE = "ORDER_UNFILLABLE"; // Order cannot be filled.
|
||||||
string constant INVALID_MAKER = "INVALID_MAKER"; // Invalid makerAddress.
|
string constant INVALID_MAKER = "INVALID_MAKER"; // Invalid makerAddress.
|
||||||
@@ -56,9 +59,11 @@ contract LibExchangeErrors {
|
|||||||
|
|
||||||
/// dispatchTransferFrom errors ///
|
/// dispatchTransferFrom errors ///
|
||||||
string constant ASSET_PROXY_DOES_NOT_EXIST = "ASSET_PROXY_DOES_NOT_EXIST"; // No assetProxy registered at given id.
|
string constant ASSET_PROXY_DOES_NOT_EXIST = "ASSET_PROXY_DOES_NOT_EXIST"; // No assetProxy registered at given id.
|
||||||
|
string constant TRANSFER_FAILED = "TRANSFER_FAILED"; // Asset transfer unsuccesful.
|
||||||
|
|
||||||
/// Length validation errors ///
|
/// Length validation errors ///
|
||||||
string constant LENGTH_GREATER_THAN_0_REQUIRED = "LENGTH_GREATER_THAN_0_REQUIRED"; // Byte array must have a length greater than 0.
|
string constant LENGTH_GREATER_THAN_0_REQUIRED = "LENGTH_GREATER_THAN_0_REQUIRED"; // Byte array must have a length greater than 0.
|
||||||
|
string constant LENGTH_GREATER_THAN_3_REQUIRED = "LENGTH_GREATER_THAN_3_REQUIRED"; // Byte array must have a length greater than 3.
|
||||||
string constant LENGTH_0_REQUIRED = "LENGTH_0_REQUIRED"; // Byte array must have a length of 0.
|
string constant LENGTH_0_REQUIRED = "LENGTH_0_REQUIRED"; // Byte array must have a length of 0.
|
||||||
string constant LENGTH_65_REQUIRED = "LENGTH_65_REQUIRED"; // Byte array must have a length of 65.
|
string constant LENGTH_65_REQUIRED = "LENGTH_65_REQUIRED"; // Byte array must have a length of 65.
|
||||||
}
|
}
|
||||||
|
@@ -23,7 +23,6 @@ import "../../../utils/SafeMath/SafeMath.sol";
|
|||||||
contract LibMath is
|
contract LibMath is
|
||||||
SafeMath
|
SafeMath
|
||||||
{
|
{
|
||||||
string constant ROUNDING_ERROR_ON_PARTIAL_AMOUNT = "A rounding error occurred when calculating partial transfer amounts.";
|
|
||||||
|
|
||||||
/// @dev Calculates partial value given a numerator and denominator.
|
/// @dev Calculates partial value given a numerator and denominator.
|
||||||
/// @param numerator Numerator.
|
/// @param numerator Numerator.
|
||||||
|
@@ -27,20 +27,18 @@ contract MAssetProxyDispatcher is
|
|||||||
|
|
||||||
// Logs registration of new asset proxy
|
// Logs registration of new asset proxy
|
||||||
event AssetProxySet(
|
event AssetProxySet(
|
||||||
uint8 id, // Id of new registered AssetProxy.
|
bytes4 id, // Id of new registered AssetProxy.
|
||||||
address newAssetProxy, // Address of new registered AssetProxy.
|
address newAssetProxy, // Address of new registered AssetProxy.
|
||||||
address oldAssetProxy // Address of AssetProxy that was overwritten at given id (or null address).
|
address oldAssetProxy // Address of AssetProxy that was overwritten at given id (or null address).
|
||||||
);
|
);
|
||||||
|
|
||||||
/// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws.
|
/// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws.
|
||||||
/// @param assetData Byte array encoded for the respective asset proxy.
|
/// @param assetData Byte array encoded for the asset.
|
||||||
/// @param assetProxyId Id of assetProxy to dispach to.
|
|
||||||
/// @param from Address to transfer token from.
|
/// @param from Address to transfer token from.
|
||||||
/// @param to Address to transfer token to.
|
/// @param to Address to transfer token to.
|
||||||
/// @param amount Amount of token to transfer.
|
/// @param amount Amount of token to transfer.
|
||||||
function dispatchTransferFrom(
|
function dispatchTransferFrom(
|
||||||
bytes memory assetData,
|
bytes memory assetData,
|
||||||
uint8 assetProxyId,
|
|
||||||
address from,
|
address from,
|
||||||
address to,
|
address to,
|
||||||
uint256 amount
|
uint256 amount
|
||||||
|
@@ -24,12 +24,11 @@ import "../../protocol/Exchange/MixinAssetProxyDispatcher.sol";
|
|||||||
contract TestAssetProxyDispatcher is MixinAssetProxyDispatcher {
|
contract TestAssetProxyDispatcher is MixinAssetProxyDispatcher {
|
||||||
function publicDispatchTransferFrom(
|
function publicDispatchTransferFrom(
|
||||||
bytes memory assetData,
|
bytes memory assetData,
|
||||||
uint8 assetProxyId,
|
|
||||||
address from,
|
address from,
|
||||||
address to,
|
address to,
|
||||||
uint256 amount)
|
uint256 amount)
|
||||||
public
|
public
|
||||||
{
|
{
|
||||||
dispatchTransferFrom(assetData, assetProxyId, from, to, amount);
|
dispatchTransferFrom(assetData, from, to, amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,17 +19,8 @@
|
|||||||
pragma solidity ^0.4.24;
|
pragma solidity ^0.4.24;
|
||||||
|
|
||||||
library LibBytes {
|
library LibBytes {
|
||||||
using LibBytes for bytes;
|
|
||||||
|
|
||||||
// Revert reasons
|
using LibBytes for bytes;
|
||||||
string constant GREATER_THAN_ZERO_LENGTH_REQUIRED = "GREATER_THAN_ZERO_LENGTH_REQUIRED";
|
|
||||||
string constant GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED";
|
|
||||||
string constant GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED";
|
|
||||||
string constant GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED";
|
|
||||||
string constant GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED";
|
|
||||||
string constant GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED";
|
|
||||||
string constant FROM_LESS_THAN_TO_REQUIRED = "FROM_LESS_THAN_TO_REQUIRED";
|
|
||||||
string constant TO_LESS_THAN_LENGTH_REQUIRED = "TO_LESS_THAN_LENGTH_REQUIRED";
|
|
||||||
|
|
||||||
/// @dev Gets the memory address for a byte array.
|
/// @dev Gets the memory address for a byte array.
|
||||||
/// @param input Byte array to lookup.
|
/// @param input Byte array to lookup.
|
||||||
@@ -176,8 +167,14 @@ library LibBytes {
|
|||||||
pure
|
pure
|
||||||
returns (bytes memory result)
|
returns (bytes memory result)
|
||||||
{
|
{
|
||||||
require(from <= to, FROM_LESS_THAN_TO_REQUIRED);
|
require(
|
||||||
require(to < b.length, TO_LESS_THAN_LENGTH_REQUIRED);
|
from <= to,
|
||||||
|
"FROM_LESS_THAN_TO_REQUIRED"
|
||||||
|
);
|
||||||
|
require(
|
||||||
|
to < b.length,
|
||||||
|
"TO_LESS_THAN_LENGTH_REQUIRED"
|
||||||
|
);
|
||||||
|
|
||||||
// Create a new bytes structure and copy contents
|
// Create a new bytes structure and copy contents
|
||||||
result = new bytes(to - from);
|
result = new bytes(to - from);
|
||||||
@@ -199,8 +196,14 @@ library LibBytes {
|
|||||||
pure
|
pure
|
||||||
returns (bytes memory result)
|
returns (bytes memory result)
|
||||||
{
|
{
|
||||||
require(from <= to, FROM_LESS_THAN_TO_REQUIRED);
|
require(
|
||||||
require(to < b.length, TO_LESS_THAN_LENGTH_REQUIRED);
|
from <= to,
|
||||||
|
"FROM_LESS_THAN_TO_REQUIRED"
|
||||||
|
);
|
||||||
|
require(
|
||||||
|
to < b.length,
|
||||||
|
"TO_LESS_THAN_LENGTH_REQUIRED"
|
||||||
|
);
|
||||||
|
|
||||||
// Create a new bytes structure around [from, to) in-place.
|
// Create a new bytes structure around [from, to) in-place.
|
||||||
assembly {
|
assembly {
|
||||||
@@ -220,7 +223,7 @@ library LibBytes {
|
|||||||
{
|
{
|
||||||
require(
|
require(
|
||||||
b.length > 0,
|
b.length > 0,
|
||||||
GREATER_THAN_ZERO_LENGTH_REQUIRED
|
"GREATER_THAN_ZERO_LENGTH_REQUIRED"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Store last byte.
|
// Store last byte.
|
||||||
@@ -244,7 +247,7 @@ library LibBytes {
|
|||||||
{
|
{
|
||||||
require(
|
require(
|
||||||
b.length >= 20,
|
b.length >= 20,
|
||||||
GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED
|
"GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Store last 20 bytes.
|
// Store last 20 bytes.
|
||||||
@@ -290,7 +293,7 @@ library LibBytes {
|
|||||||
{
|
{
|
||||||
require(
|
require(
|
||||||
b.length >= index + 20, // 20 is length of address
|
b.length >= index + 20, // 20 is length of address
|
||||||
GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED
|
"GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add offset to index:
|
// Add offset to index:
|
||||||
@@ -322,7 +325,7 @@ library LibBytes {
|
|||||||
{
|
{
|
||||||
require(
|
require(
|
||||||
b.length >= index + 20, // 20 is length of address
|
b.length >= index + 20, // 20 is length of address
|
||||||
GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED
|
"GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add offset to index:
|
// Add offset to index:
|
||||||
@@ -365,7 +368,7 @@ library LibBytes {
|
|||||||
{
|
{
|
||||||
require(
|
require(
|
||||||
b.length >= index + 32,
|
b.length >= index + 32,
|
||||||
GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED
|
"GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Arrays are prefixed by a 256 bit length parameter
|
// Arrays are prefixed by a 256 bit length parameter
|
||||||
@@ -392,7 +395,7 @@ library LibBytes {
|
|||||||
{
|
{
|
||||||
require(
|
require(
|
||||||
b.length >= index + 32,
|
b.length >= index + 32,
|
||||||
GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED
|
"GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Arrays are prefixed by a 256 bit length parameter
|
// Arrays are prefixed by a 256 bit length parameter
|
||||||
@@ -447,7 +450,7 @@ library LibBytes {
|
|||||||
{
|
{
|
||||||
require(
|
require(
|
||||||
b.length >= index + 4,
|
b.length >= index + 4,
|
||||||
GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED
|
"GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED"
|
||||||
);
|
);
|
||||||
assembly {
|
assembly {
|
||||||
result := mload(add(b, 32))
|
result := mload(add(b, 32))
|
||||||
@@ -459,6 +462,8 @@ library LibBytes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Reads nested bytes from a specific position.
|
/// @dev Reads nested bytes from a specific position.
|
||||||
|
/// @dev NOTE: the returned value overlaps with the input value.
|
||||||
|
/// Both should be treated as immutable.
|
||||||
/// @param b Byte array containing nested bytes.
|
/// @param b Byte array containing nested bytes.
|
||||||
/// @param index Index of nested bytes.
|
/// @param index Index of nested bytes.
|
||||||
/// @return result Nested bytes.
|
/// @return result Nested bytes.
|
||||||
@@ -478,17 +483,13 @@ library LibBytes {
|
|||||||
// length of nested bytes
|
// length of nested bytes
|
||||||
require(
|
require(
|
||||||
b.length >= index + nestedBytesLength,
|
b.length >= index + nestedBytesLength,
|
||||||
GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED
|
"GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED"
|
||||||
);
|
|
||||||
|
|
||||||
// Allocate memory and copy value to result
|
|
||||||
result = new bytes(nestedBytesLength);
|
|
||||||
memCopy(
|
|
||||||
result.contentAddress(),
|
|
||||||
b.contentAddress() + index,
|
|
||||||
nestedBytesLength
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Return a pointer to the byte array as it exists inside `b`
|
||||||
|
assembly {
|
||||||
|
result := add(b, index)
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,7 +509,7 @@ library LibBytes {
|
|||||||
// length of input
|
// length of input
|
||||||
require(
|
require(
|
||||||
b.length >= index + 32 /* 32 bytes to store length */ + input.length,
|
b.length >= index + 32 /* 32 bytes to store length */ + input.length,
|
||||||
GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED
|
"GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Copy <input> into <b>
|
// Copy <input> into <b>
|
||||||
@@ -533,7 +534,7 @@ library LibBytes {
|
|||||||
// Dest length must be >= source length, or some bytes would not be copied.
|
// Dest length must be >= source length, or some bytes would not be copied.
|
||||||
require(
|
require(
|
||||||
dest.length >= sourceLen,
|
dest.length >= sourceLen,
|
||||||
GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED
|
"GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED"
|
||||||
);
|
);
|
||||||
memCopy(
|
memCopy(
|
||||||
dest.contentAddress(),
|
dest.contentAddress(),
|
||||||
|
@@ -13,9 +13,6 @@ import "./IOwnable.sol";
|
|||||||
contract Ownable is IOwnable {
|
contract Ownable is IOwnable {
|
||||||
address public owner;
|
address public owner;
|
||||||
|
|
||||||
// Revert reasons
|
|
||||||
string constant ONLY_CONTRACT_OWNER = "ONLY_CONTRACT_OWNER";
|
|
||||||
|
|
||||||
constructor ()
|
constructor ()
|
||||||
public
|
public
|
||||||
{
|
{
|
||||||
@@ -25,7 +22,7 @@ contract Ownable is IOwnable {
|
|||||||
modifier onlyOwner() {
|
modifier onlyOwner() {
|
||||||
require(
|
require(
|
||||||
msg.sender == owner,
|
msg.sender == owner,
|
||||||
ONLY_CONTRACT_OWNER
|
"ONLY_CONTRACT_OWNER"
|
||||||
);
|
);
|
||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
|
@@ -44,7 +44,6 @@ describe('TestAssetDataDecoders', () => {
|
|||||||
it('should correctly decode ERC721 asset data', async () => {
|
it('should correctly decode ERC721 asset data', async () => {
|
||||||
const tokenId = generatePseudoRandomSalt();
|
const tokenId = generatePseudoRandomSalt();
|
||||||
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(testAddress, tokenId);
|
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(testAddress, tokenId);
|
||||||
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
|
|
||||||
const expectedDecodedAssetData = assetProxyUtils.decodeERC721AssetData(encodedAssetData);
|
const expectedDecodedAssetData = assetProxyUtils.decodeERC721AssetData(encodedAssetData);
|
||||||
let decodedTokenAddress: string;
|
let decodedTokenAddress: string;
|
||||||
let decodedTokenId: BigNumber;
|
let decodedTokenId: BigNumber;
|
||||||
@@ -53,7 +52,7 @@ describe('TestAssetDataDecoders', () => {
|
|||||||
decodedTokenAddress,
|
decodedTokenAddress,
|
||||||
decodedTokenId,
|
decodedTokenId,
|
||||||
decodedData,
|
decodedData,
|
||||||
] = await testAssetProxyDecoder.publicDecodeERC721Data.callAsync(encodedAssetDataWithoutProxyId);
|
] = await testAssetProxyDecoder.publicDecodeERC721Data.callAsync(encodedAssetData);
|
||||||
expect(decodedTokenAddress).to.be.equal(expectedDecodedAssetData.tokenAddress);
|
expect(decodedTokenAddress).to.be.equal(expectedDecodedAssetData.tokenAddress);
|
||||||
expect(decodedTokenId).to.be.bignumber.equal(expectedDecodedAssetData.tokenId);
|
expect(decodedTokenId).to.be.bignumber.equal(expectedDecodedAssetData.tokenId);
|
||||||
expect(decodedData).to.be.equal(expectedDecodedAssetData.receiverData);
|
expect(decodedData).to.be.equal(expectedDecodedAssetData.receiverData);
|
||||||
|
@@ -96,13 +96,12 @@ describe('Asset Transfer Proxies', () => {
|
|||||||
it('should successfully transfer tokens', async () => {
|
it('should successfully transfer tokens', async () => {
|
||||||
// Construct ERC20 asset data
|
// Construct ERC20 asset data
|
||||||
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
|
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
|
||||||
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
|
|
||||||
// Perform a transfer from makerAddress to takerAddress
|
// Perform a transfer from makerAddress to takerAddress
|
||||||
const erc20Balances = await erc20Wrapper.getBalancesAsync();
|
const erc20Balances = await erc20Wrapper.getBalancesAsync();
|
||||||
const amount = new BigNumber(10);
|
const amount = new BigNumber(10);
|
||||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||||
await erc20Proxy.transferFrom.sendTransactionAsync(
|
await erc20Proxy.transferFrom.sendTransactionAsync(
|
||||||
encodedAssetDataWithoutProxyId,
|
encodedAssetData,
|
||||||
makerAddress,
|
makerAddress,
|
||||||
takerAddress,
|
takerAddress,
|
||||||
amount,
|
amount,
|
||||||
@@ -123,13 +122,12 @@ describe('Asset Transfer Proxies', () => {
|
|||||||
it('should do nothing if transferring 0 amount of a token', async () => {
|
it('should do nothing if transferring 0 amount of a token', async () => {
|
||||||
// Construct ERC20 asset data
|
// Construct ERC20 asset data
|
||||||
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
|
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
|
||||||
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
|
|
||||||
// Perform a transfer from makerAddress to takerAddress
|
// Perform a transfer from makerAddress to takerAddress
|
||||||
const erc20Balances = await erc20Wrapper.getBalancesAsync();
|
const erc20Balances = await erc20Wrapper.getBalancesAsync();
|
||||||
const amount = new BigNumber(0);
|
const amount = new BigNumber(0);
|
||||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||||
await erc20Proxy.transferFrom.sendTransactionAsync(
|
await erc20Proxy.transferFrom.sendTransactionAsync(
|
||||||
encodedAssetDataWithoutProxyId,
|
encodedAssetData,
|
||||||
makerAddress,
|
makerAddress,
|
||||||
takerAddress,
|
takerAddress,
|
||||||
amount,
|
amount,
|
||||||
@@ -174,19 +172,13 @@ describe('Asset Transfer Proxies', () => {
|
|||||||
it('should throw if requesting address is not authorized', async () => {
|
it('should throw if requesting address is not authorized', async () => {
|
||||||
// Construct ERC20 asset data
|
// Construct ERC20 asset data
|
||||||
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
|
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
|
||||||
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
|
|
||||||
// Perform a transfer from makerAddress to takerAddress
|
// Perform a transfer from makerAddress to takerAddress
|
||||||
const amount = new BigNumber(10);
|
const amount = new BigNumber(10);
|
||||||
return expectRevertOrAlwaysFailingTransactionAsync(
|
return expectRevertOrAlwaysFailingTransactionAsync(
|
||||||
erc20Proxy.transferFrom.sendTransactionAsync(
|
erc20Proxy.transferFrom.sendTransactionAsync(encodedAssetData, makerAddress, takerAddress, amount, {
|
||||||
encodedAssetDataWithoutProxyId,
|
from: notAuthorized,
|
||||||
makerAddress,
|
}),
|
||||||
takerAddress,
|
|
||||||
amount,
|
|
||||||
{
|
|
||||||
from: notAuthorized,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -196,10 +188,9 @@ describe('Asset Transfer Proxies', () => {
|
|||||||
const erc20Balances = await erc20Wrapper.getBalancesAsync();
|
const erc20Balances = await erc20Wrapper.getBalancesAsync();
|
||||||
|
|
||||||
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
|
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
|
||||||
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
|
|
||||||
const amount = new BigNumber(10);
|
const amount = new BigNumber(10);
|
||||||
const numTransfers = 2;
|
const numTransfers = 2;
|
||||||
const assetData = _.times(numTransfers, () => encodedAssetDataWithoutProxyId);
|
const assetData = _.times(numTransfers, () => encodedAssetData);
|
||||||
const fromAddresses = _.times(numTransfers, () => makerAddress);
|
const fromAddresses = _.times(numTransfers, () => makerAddress);
|
||||||
const toAddresses = _.times(numTransfers, () => takerAddress);
|
const toAddresses = _.times(numTransfers, () => takerAddress);
|
||||||
const amounts = _.times(numTransfers, () => amount);
|
const amounts = _.times(numTransfers, () => amount);
|
||||||
@@ -228,10 +219,9 @@ describe('Asset Transfer Proxies', () => {
|
|||||||
|
|
||||||
it('should throw if not called by an authorized address', async () => {
|
it('should throw if not called by an authorized address', async () => {
|
||||||
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
|
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
|
||||||
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
|
|
||||||
const amount = new BigNumber(10);
|
const amount = new BigNumber(10);
|
||||||
const numTransfers = 2;
|
const numTransfers = 2;
|
||||||
const assetData = _.times(numTransfers, () => encodedAssetDataWithoutProxyId);
|
const assetData = _.times(numTransfers, () => encodedAssetData);
|
||||||
const fromAddresses = _.times(numTransfers, () => makerAddress);
|
const fromAddresses = _.times(numTransfers, () => makerAddress);
|
||||||
const toAddresses = _.times(numTransfers, () => takerAddress);
|
const toAddresses = _.times(numTransfers, () => takerAddress);
|
||||||
const amounts = _.times(numTransfers, () => amount);
|
const amounts = _.times(numTransfers, () => amount);
|
||||||
@@ -244,9 +234,10 @@ describe('Asset Transfer Proxies', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have an id of 1', async () => {
|
it('should have an id of 0xf47261b0', async () => {
|
||||||
const proxyId = await erc20Proxy.getProxyId.callAsync();
|
const proxyId = await erc20Proxy.getProxyId.callAsync();
|
||||||
expect(proxyId).to.equal(1);
|
const expectedProxyId = '0xf47261b0';
|
||||||
|
expect(proxyId).to.equal(expectedProxyId);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -255,7 +246,6 @@ describe('Asset Transfer Proxies', () => {
|
|||||||
it('should successfully transfer tokens', async () => {
|
it('should successfully transfer tokens', async () => {
|
||||||
// Construct ERC721 asset data
|
// Construct ERC721 asset data
|
||||||
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
|
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
|
||||||
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
|
|
||||||
// Verify pre-condition
|
// Verify pre-condition
|
||||||
const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
|
const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
|
||||||
expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
|
expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
|
||||||
@@ -263,7 +253,7 @@ describe('Asset Transfer Proxies', () => {
|
|||||||
const amount = new BigNumber(1);
|
const amount = new BigNumber(1);
|
||||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||||
await erc721Proxy.transferFrom.sendTransactionAsync(
|
await erc721Proxy.transferFrom.sendTransactionAsync(
|
||||||
encodedAssetDataWithoutProxyId,
|
encodedAssetData,
|
||||||
makerAddress,
|
makerAddress,
|
||||||
takerAddress,
|
takerAddress,
|
||||||
amount,
|
amount,
|
||||||
@@ -279,14 +269,13 @@ describe('Asset Transfer Proxies', () => {
|
|||||||
it('should call onERC721Received when transferring to a smart contract without receiver data', async () => {
|
it('should call onERC721Received when transferring to a smart contract without receiver data', async () => {
|
||||||
// Construct ERC721 asset data
|
// Construct ERC721 asset data
|
||||||
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
|
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
|
||||||
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
|
|
||||||
// Verify pre-condition
|
// Verify pre-condition
|
||||||
const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
|
const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
|
||||||
expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
|
expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
|
||||||
// Perform a transfer from makerAddress to takerAddress
|
// Perform a transfer from makerAddress to takerAddress
|
||||||
const amount = new BigNumber(1);
|
const amount = new BigNumber(1);
|
||||||
const txHash = await erc721Proxy.transferFrom.sendTransactionAsync(
|
const txHash = await erc721Proxy.transferFrom.sendTransactionAsync(
|
||||||
encodedAssetDataWithoutProxyId,
|
encodedAssetData,
|
||||||
makerAddress,
|
makerAddress,
|
||||||
erc721Receiver.address,
|
erc721Receiver.address,
|
||||||
amount,
|
amount,
|
||||||
@@ -315,14 +304,13 @@ describe('Asset Transfer Proxies', () => {
|
|||||||
erc721MakerTokenId,
|
erc721MakerTokenId,
|
||||||
receiverData,
|
receiverData,
|
||||||
);
|
);
|
||||||
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
|
|
||||||
// Verify pre-condition
|
// Verify pre-condition
|
||||||
const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
|
const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
|
||||||
expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
|
expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
|
||||||
// Perform a transfer from makerAddress to takerAddress
|
// Perform a transfer from makerAddress to takerAddress
|
||||||
const amount = new BigNumber(1);
|
const amount = new BigNumber(1);
|
||||||
const txHash = await erc721Proxy.transferFrom.sendTransactionAsync(
|
const txHash = await erc721Proxy.transferFrom.sendTransactionAsync(
|
||||||
encodedAssetDataWithoutProxyId,
|
encodedAssetData,
|
||||||
makerAddress,
|
makerAddress,
|
||||||
erc721Receiver.address,
|
erc721Receiver.address,
|
||||||
amount,
|
amount,
|
||||||
@@ -351,7 +339,6 @@ describe('Asset Transfer Proxies', () => {
|
|||||||
erc721MakerTokenId,
|
erc721MakerTokenId,
|
||||||
receiverData,
|
receiverData,
|
||||||
);
|
);
|
||||||
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
|
|
||||||
// Verify pre-condition
|
// Verify pre-condition
|
||||||
const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
|
const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
|
||||||
expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
|
expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
|
||||||
@@ -359,7 +346,7 @@ describe('Asset Transfer Proxies', () => {
|
|||||||
const amount = new BigNumber(1);
|
const amount = new BigNumber(1);
|
||||||
return expectRevertOrAlwaysFailingTransactionAsync(
|
return expectRevertOrAlwaysFailingTransactionAsync(
|
||||||
erc721Proxy.transferFrom.sendTransactionAsync(
|
erc721Proxy.transferFrom.sendTransactionAsync(
|
||||||
encodedAssetDataWithoutProxyId,
|
encodedAssetData,
|
||||||
makerAddress,
|
makerAddress,
|
||||||
erc20Proxy.address, // the ERC20 proxy does not have an ERC721 receiver
|
erc20Proxy.address, // the ERC20 proxy does not have an ERC721 receiver
|
||||||
amount,
|
amount,
|
||||||
@@ -371,7 +358,6 @@ describe('Asset Transfer Proxies', () => {
|
|||||||
it('should throw if transferring 0 amount of a token', async () => {
|
it('should throw if transferring 0 amount of a token', async () => {
|
||||||
// Construct ERC721 asset data
|
// Construct ERC721 asset data
|
||||||
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
|
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
|
||||||
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
|
|
||||||
// Verify pre-condition
|
// Verify pre-condition
|
||||||
const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
|
const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
|
||||||
expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
|
expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
|
||||||
@@ -379,7 +365,7 @@ describe('Asset Transfer Proxies', () => {
|
|||||||
const amount = new BigNumber(0);
|
const amount = new BigNumber(0);
|
||||||
return expectRevertOrAlwaysFailingTransactionAsync(
|
return expectRevertOrAlwaysFailingTransactionAsync(
|
||||||
erc721Proxy.transferFrom.sendTransactionAsync(
|
erc721Proxy.transferFrom.sendTransactionAsync(
|
||||||
encodedAssetDataWithoutProxyId,
|
encodedAssetData,
|
||||||
makerAddress,
|
makerAddress,
|
||||||
takerAddress,
|
takerAddress,
|
||||||
amount,
|
amount,
|
||||||
@@ -391,7 +377,6 @@ describe('Asset Transfer Proxies', () => {
|
|||||||
it('should throw if transferring > 1 amount of a token', async () => {
|
it('should throw if transferring > 1 amount of a token', async () => {
|
||||||
// Construct ERC721 asset data
|
// Construct ERC721 asset data
|
||||||
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
|
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
|
||||||
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
|
|
||||||
// Verify pre-condition
|
// Verify pre-condition
|
||||||
const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
|
const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
|
||||||
expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
|
expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
|
||||||
@@ -399,7 +384,7 @@ describe('Asset Transfer Proxies', () => {
|
|||||||
const amount = new BigNumber(500);
|
const amount = new BigNumber(500);
|
||||||
return expectRevertOrAlwaysFailingTransactionAsync(
|
return expectRevertOrAlwaysFailingTransactionAsync(
|
||||||
erc721Proxy.transferFrom.sendTransactionAsync(
|
erc721Proxy.transferFrom.sendTransactionAsync(
|
||||||
encodedAssetDataWithoutProxyId,
|
encodedAssetData,
|
||||||
makerAddress,
|
makerAddress,
|
||||||
takerAddress,
|
takerAddress,
|
||||||
amount,
|
amount,
|
||||||
@@ -411,7 +396,6 @@ describe('Asset Transfer Proxies', () => {
|
|||||||
it('should throw if allowances are too low', async () => {
|
it('should throw if allowances are too low', async () => {
|
||||||
// Construct ERC721 asset data
|
// Construct ERC721 asset data
|
||||||
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
|
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
|
||||||
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
|
|
||||||
// Remove transfer approval for makerAddress.
|
// Remove transfer approval for makerAddress.
|
||||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||||
await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, false, {
|
await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, false, {
|
||||||
@@ -422,27 +406,20 @@ describe('Asset Transfer Proxies', () => {
|
|||||||
// Perform a transfer; expect this to fail.
|
// Perform a transfer; expect this to fail.
|
||||||
const amount = new BigNumber(1);
|
const amount = new BigNumber(1);
|
||||||
return expectRevertOrAlwaysFailingTransactionAsync(
|
return expectRevertOrAlwaysFailingTransactionAsync(
|
||||||
erc20Proxy.transferFrom.sendTransactionAsync(
|
erc20Proxy.transferFrom.sendTransactionAsync(encodedAssetData, makerAddress, takerAddress, amount, {
|
||||||
encodedAssetDataWithoutProxyId,
|
from: notAuthorized,
|
||||||
makerAddress,
|
}),
|
||||||
takerAddress,
|
|
||||||
amount,
|
|
||||||
{
|
|
||||||
from: notAuthorized,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw if requesting address is not authorized', async () => {
|
it('should throw if requesting address is not authorized', async () => {
|
||||||
// Construct ERC721 asset data
|
// Construct ERC721 asset data
|
||||||
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
|
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
|
||||||
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
|
|
||||||
// Perform a transfer from makerAddress to takerAddress
|
// Perform a transfer from makerAddress to takerAddress
|
||||||
const amount = new BigNumber(1);
|
const amount = new BigNumber(1);
|
||||||
return expectRevertOrAlwaysFailingTransactionAsync(
|
return expectRevertOrAlwaysFailingTransactionAsync(
|
||||||
erc721Proxy.transferFrom.sendTransactionAsync(
|
erc721Proxy.transferFrom.sendTransactionAsync(
|
||||||
encodedAssetDataWithoutProxyId,
|
encodedAssetData,
|
||||||
makerAddress,
|
makerAddress,
|
||||||
takerAddress,
|
takerAddress,
|
||||||
amount,
|
amount,
|
||||||
@@ -459,8 +436,8 @@ describe('Asset Transfer Proxies', () => {
|
|||||||
|
|
||||||
const numTransfers = 2;
|
const numTransfers = 2;
|
||||||
const assetData = [
|
const assetData = [
|
||||||
assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdA).slice(0, -2),
|
assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdA),
|
||||||
assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdB).slice(0, -2),
|
assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdB),
|
||||||
];
|
];
|
||||||
const fromAddresses = _.times(numTransfers, () => makerAddress);
|
const fromAddresses = _.times(numTransfers, () => makerAddress);
|
||||||
const toAddresses = _.times(numTransfers, () => takerAddress);
|
const toAddresses = _.times(numTransfers, () => takerAddress);
|
||||||
@@ -506,9 +483,10 @@ describe('Asset Transfer Proxies', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have an id of 2', async () => {
|
it('should have an id of 0x08e937fa', async () => {
|
||||||
const proxyId = await erc721Proxy.getProxyId.callAsync();
|
const proxyId = await erc721Proxy.getProxyId.callAsync();
|
||||||
expect(proxyId).to.equal(2);
|
const expectedProxyId = '0x08e937fa';
|
||||||
|
expect(proxyId).to.equal(expectedProxyId);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -86,7 +86,7 @@ describe('Exchange core', () => {
|
|||||||
artifacts.Exchange,
|
artifacts.Exchange,
|
||||||
provider,
|
provider,
|
||||||
txDefaults,
|
txDefaults,
|
||||||
zrxToken.address,
|
assetProxyUtils.encodeERC20AssetData(zrxToken.address),
|
||||||
);
|
);
|
||||||
exchangeWrapper = new ExchangeWrapper(exchange, provider);
|
exchangeWrapper = new ExchangeWrapper(exchange, provider);
|
||||||
await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner);
|
await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner);
|
||||||
|
@@ -276,14 +276,13 @@ describe('AssetProxyDispatcher', () => {
|
|||||||
);
|
);
|
||||||
// Construct metadata for ERC20 proxy
|
// Construct metadata for ERC20 proxy
|
||||||
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
|
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
|
||||||
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
|
|
||||||
// Perform a transfer from makerAddress to takerAddress
|
// Perform a transfer from makerAddress to takerAddress
|
||||||
const erc20Balances = await erc20Wrapper.getBalancesAsync();
|
const erc20Balances = await erc20Wrapper.getBalancesAsync();
|
||||||
const amount = new BigNumber(10);
|
const amount = new BigNumber(10);
|
||||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||||
await assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync(
|
await assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync(
|
||||||
encodedAssetDataWithoutProxyId,
|
encodedAssetData,
|
||||||
AssetProxyId.ERC20,
|
|
||||||
makerAddress,
|
makerAddress,
|
||||||
takerAddress,
|
takerAddress,
|
||||||
amount,
|
amount,
|
||||||
@@ -304,13 +303,11 @@ describe('AssetProxyDispatcher', () => {
|
|||||||
it('should throw if dispatching to unregistered proxy', async () => {
|
it('should throw if dispatching to unregistered proxy', async () => {
|
||||||
// Construct metadata for ERC20 proxy
|
// Construct metadata for ERC20 proxy
|
||||||
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
|
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
|
||||||
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
|
|
||||||
// Perform a transfer from makerAddress to takerAddress
|
// Perform a transfer from makerAddress to takerAddress
|
||||||
const amount = new BigNumber(10);
|
const amount = new BigNumber(10);
|
||||||
return expectRevertOrAlwaysFailingTransactionAsync(
|
return expectRevertOrAlwaysFailingTransactionAsync(
|
||||||
assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync(
|
assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync(
|
||||||
encodedAssetDataWithoutProxyId,
|
encodedAssetData,
|
||||||
AssetProxyId.ERC20,
|
|
||||||
makerAddress,
|
makerAddress,
|
||||||
takerAddress,
|
takerAddress,
|
||||||
amount,
|
amount,
|
||||||
|
@@ -96,7 +96,7 @@ describe('matchOrders', () => {
|
|||||||
artifacts.Exchange,
|
artifacts.Exchange,
|
||||||
provider,
|
provider,
|
||||||
txDefaults,
|
txDefaults,
|
||||||
zrxToken.address,
|
assetProxyUtils.encodeERC20AssetData(zrxToken.address),
|
||||||
);
|
);
|
||||||
exchangeWrapper = new ExchangeWrapper(exchange, provider);
|
exchangeWrapper = new ExchangeWrapper(exchange, provider);
|
||||||
await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner);
|
await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner);
|
||||||
|
@@ -73,7 +73,7 @@ describe('Exchange transactions', () => {
|
|||||||
artifacts.Exchange,
|
artifacts.Exchange,
|
||||||
provider,
|
provider,
|
||||||
txDefaults,
|
txDefaults,
|
||||||
zrxToken.address,
|
assetProxyUtils.encodeERC20AssetData(zrxToken.address),
|
||||||
);
|
);
|
||||||
exchangeWrapper = new ExchangeWrapper(exchange, provider);
|
exchangeWrapper = new ExchangeWrapper(exchange, provider);
|
||||||
await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner);
|
await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner);
|
||||||
|
@@ -80,7 +80,7 @@ describe('Exchange wrappers', () => {
|
|||||||
artifacts.Exchange,
|
artifacts.Exchange,
|
||||||
provider,
|
provider,
|
||||||
txDefaults,
|
txDefaults,
|
||||||
zrxToken.address,
|
assetProxyUtils.encodeERC20AssetData(zrxToken.address),
|
||||||
);
|
);
|
||||||
exchangeWrapper = new ExchangeWrapper(exchange, provider);
|
exchangeWrapper = new ExchangeWrapper(exchange, provider);
|
||||||
await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner);
|
await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner);
|
||||||
|
@@ -1,32 +1,43 @@
|
|||||||
import { AssetProxyId, ERC20AssetData, ERC721AssetData } from '@0xproject/types';
|
import { AssetProxyId, ERC20AssetData, ERC721AssetData } from '@0xproject/types';
|
||||||
import { BigNumber, NULL_BYTES } from '@0xproject/utils';
|
import { BigNumber } from '@0xproject/utils';
|
||||||
import BN = require('bn.js');
|
import BN = require('bn.js');
|
||||||
import ethUtil = require('ethereumjs-util');
|
import ethUtil = require('ethereumjs-util');
|
||||||
import * as _ from 'lodash';
|
|
||||||
|
|
||||||
const ERC20_ASSET_DATA_BYTE_LENGTH = 21;
|
import { constants } from './constants';
|
||||||
const ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH = 53;
|
|
||||||
const ASSET_DATA_ADDRESS_OFFSET = 0;
|
// TODO: Push upstream to DefinitelyTyped
|
||||||
const ERC721_ASSET_DATA_TOKEN_ID_OFFSET = 20;
|
interface EthAbi {
|
||||||
const ERC721_ASSET_DATA_RECEIVER_DATA_LENGTH_OFFSET = 52;
|
simpleEncode(signature: string, ...args: any[]): Buffer;
|
||||||
const ERC721_ASSET_DATA_RECEIVER_DATA_OFFSET = 84;
|
rawDecode(signature: string[], data: Buffer): any[];
|
||||||
|
}
|
||||||
|
// tslint:disable:no-var-requires
|
||||||
|
const ethAbi = require('ethereumjs-abi') as EthAbi;
|
||||||
|
|
||||||
export const assetProxyUtils = {
|
export const assetProxyUtils = {
|
||||||
encodeAssetProxyId(assetProxyId: AssetProxyId): Buffer {
|
encodeAssetProxyId(assetProxyId: AssetProxyId): Buffer {
|
||||||
return ethUtil.toBuffer(assetProxyId);
|
return ethUtil.toBuffer(assetProxyId);
|
||||||
},
|
},
|
||||||
decodeAssetProxyId(encodedAssetProxyId: Buffer): AssetProxyId {
|
decodeAssetProxyId(encodedAssetProxyId: Buffer): AssetProxyId {
|
||||||
return ethUtil.bufferToInt(encodedAssetProxyId);
|
const hexString = ethUtil.bufferToHex(encodedAssetProxyId);
|
||||||
|
if (hexString === AssetProxyId.ERC20) {
|
||||||
|
return AssetProxyId.ERC20;
|
||||||
|
}
|
||||||
|
if (hexString === AssetProxyId.ERC721) {
|
||||||
|
return AssetProxyId.ERC721;
|
||||||
|
}
|
||||||
|
throw new Error(`Invalid ProxyId: ${hexString}`);
|
||||||
},
|
},
|
||||||
encodeAddress(address: string): Buffer {
|
encodeAddress(address: string): Buffer {
|
||||||
if (!ethUtil.isValidAddress(address)) {
|
if (!ethUtil.isValidAddress(address)) {
|
||||||
throw new Error(`Invalid Address: ${address}`);
|
throw new Error(`Invalid Address: ${address}`);
|
||||||
}
|
}
|
||||||
const encodedAddress = ethUtil.toBuffer(address);
|
const encodedAddress = ethUtil.toBuffer(address);
|
||||||
return encodedAddress;
|
const padded = ethUtil.setLengthLeft(encodedAddress, constants.WORD_LENGTH);
|
||||||
|
return padded;
|
||||||
},
|
},
|
||||||
decodeAddress(encodedAddress: Buffer): string {
|
decodeAddress(encodedAddress: Buffer): string {
|
||||||
const address = ethUtil.bufferToHex(encodedAddress);
|
const unpadded = ethUtil.setLengthLeft(encodedAddress, constants.ADDRESS_LENGTH);
|
||||||
|
const address = ethUtil.bufferToHex(unpadded);
|
||||||
if (!ethUtil.isValidAddress(address)) {
|
if (!ethUtil.isValidAddress(address)) {
|
||||||
throw new Error(`Invalid Address: ${address}`);
|
throw new Error(`Invalid Address: ${address}`);
|
||||||
}
|
}
|
||||||
@@ -37,33 +48,27 @@ export const assetProxyUtils = {
|
|||||||
const formattedValue = new BN(value.toString(base));
|
const formattedValue = new BN(value.toString(base));
|
||||||
const encodedValue = ethUtil.toBuffer(formattedValue);
|
const encodedValue = ethUtil.toBuffer(formattedValue);
|
||||||
// tslint:disable-next-line:custom-no-magic-numbers
|
// tslint:disable-next-line:custom-no-magic-numbers
|
||||||
const paddedValue = ethUtil.setLengthLeft(encodedValue, 32);
|
const paddedValue = ethUtil.setLengthLeft(encodedValue, constants.WORD_LENGTH);
|
||||||
return paddedValue;
|
return paddedValue;
|
||||||
},
|
},
|
||||||
decodeUint256(encodedValue: Buffer): BigNumber {
|
decodeUint256(encodedValue: Buffer): BigNumber {
|
||||||
const formattedValue = ethUtil.bufferToHex(encodedValue);
|
const formattedValue = ethUtil.bufferToHex(encodedValue);
|
||||||
const value = new BigNumber(formattedValue, 16);
|
const value = new BigNumber(formattedValue, constants.BASE_16);
|
||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
encodeERC20AssetData(tokenAddress: string): string {
|
encodeERC20AssetData(tokenAddress: string): string {
|
||||||
const encodedAssetProxyId = assetProxyUtils.encodeAssetProxyId(AssetProxyId.ERC20);
|
return ethUtil.bufferToHex(ethAbi.simpleEncode('ERC20Token(address)', tokenAddress));
|
||||||
const encodedAddress = assetProxyUtils.encodeAddress(tokenAddress);
|
|
||||||
const encodedAssetData = Buffer.concat([encodedAddress, encodedAssetProxyId]);
|
|
||||||
const encodedAssetDataHex = ethUtil.bufferToHex(encodedAssetData);
|
|
||||||
return encodedAssetDataHex;
|
|
||||||
},
|
},
|
||||||
decodeERC20AssetData(proxyData: string): ERC20AssetData {
|
decodeERC20AssetData(assetData: string): ERC20AssetData {
|
||||||
const encodedAssetData = ethUtil.toBuffer(proxyData);
|
const data = ethUtil.toBuffer(assetData);
|
||||||
if (encodedAssetData.byteLength !== ERC20_ASSET_DATA_BYTE_LENGTH) {
|
if (data.byteLength < constants.ERC20_ASSET_DATA_BYTE_LENGTH) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Could not decode ERC20 Proxy Data. Expected length of encoded data to be 21. Got ${
|
`Could not decode ERC20 Proxy Data. Expected length of encoded data to be at least ${
|
||||||
encodedAssetData.byteLength
|
constants.ERC20_ASSET_DATA_BYTE_LENGTH
|
||||||
}`,
|
}. Got ${data.byteLength}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const assetProxyIdOffset = encodedAssetData.byteLength - 1;
|
const assetProxyId = ethUtil.bufferToHex(data.slice(0, constants.SELECTOR_LENGTH));
|
||||||
const encodedAssetProxyId = encodedAssetData.slice(assetProxyIdOffset);
|
|
||||||
const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId);
|
|
||||||
if (assetProxyId !== AssetProxyId.ERC20) {
|
if (assetProxyId !== AssetProxyId.ERC20) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Could not decode ERC20 Proxy Data. Expected Asset Proxy Id to be ERC20 (${
|
`Could not decode ERC20 Proxy Data. Expected Asset Proxy Id to be ERC20 (${
|
||||||
@@ -71,98 +76,61 @@ export const assetProxyUtils = {
|
|||||||
}), but got ${assetProxyId}`,
|
}), but got ${assetProxyId}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const encodedTokenAddress = encodedAssetData.slice(ASSET_DATA_ADDRESS_OFFSET, assetProxyIdOffset);
|
const [tokenAddress] = ethAbi.rawDecode(['address'], data.slice(constants.SELECTOR_LENGTH));
|
||||||
const tokenAddress = assetProxyUtils.decodeAddress(encodedTokenAddress);
|
return {
|
||||||
const erc20AssetData = {
|
|
||||||
assetProxyId,
|
assetProxyId,
|
||||||
tokenAddress,
|
tokenAddress: ethUtil.addHexPrefix(tokenAddress),
|
||||||
};
|
};
|
||||||
return erc20AssetData;
|
|
||||||
},
|
},
|
||||||
encodeERC721AssetData(tokenAddress: string, tokenId: BigNumber, receiverData?: string): string {
|
encodeERC721AssetData(tokenAddress: string, tokenId: BigNumber, receiverData?: string): string {
|
||||||
const encodedAssetProxyId = assetProxyUtils.encodeAssetProxyId(AssetProxyId.ERC721);
|
// TODO: Pass `tokendId` as a BigNumber.
|
||||||
const encodedAddress = assetProxyUtils.encodeAddress(tokenAddress);
|
return ethUtil.bufferToHex(
|
||||||
const encodedTokenId = assetProxyUtils.encodeUint256(tokenId);
|
ethAbi.simpleEncode(
|
||||||
let encodedAssetData = Buffer.concat([encodedAddress, encodedTokenId]);
|
'ERC721Token(address,uint256,bytes)',
|
||||||
if (!_.isUndefined(receiverData)) {
|
tokenAddress,
|
||||||
const encodedReceiverData = ethUtil.toBuffer(receiverData);
|
`0x${tokenId.toString(constants.BASE_16)}`,
|
||||||
const receiverDataLength = new BigNumber(encodedReceiverData.byteLength);
|
ethUtil.toBuffer(receiverData || '0x'),
|
||||||
const encodedReceiverDataLength = assetProxyUtils.encodeUint256(receiverDataLength);
|
),
|
||||||
encodedAssetData = Buffer.concat([encodedAssetData, encodedReceiverDataLength, encodedReceiverData]);
|
);
|
||||||
}
|
|
||||||
encodedAssetData = Buffer.concat([encodedAssetData, encodedAssetProxyId]);
|
|
||||||
const encodedAssetDataHex = ethUtil.bufferToHex(encodedAssetData);
|
|
||||||
return encodedAssetDataHex;
|
|
||||||
},
|
},
|
||||||
decodeERC721AssetData(assetData: string): ERC721AssetData {
|
decodeERC721AssetData(assetData: string): ERC721AssetData {
|
||||||
const encodedAssetData = ethUtil.toBuffer(assetData);
|
const data = ethUtil.toBuffer(assetData);
|
||||||
if (encodedAssetData.byteLength < ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH) {
|
if (data.byteLength < constants.ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Could not decode ERC20 Proxy Data. Expected length of encoded data to be at least 53. Got ${
|
`Could not decode ERC721 Asset Data. Expected length of encoded data to be at least ${
|
||||||
encodedAssetData.byteLength
|
constants.ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH
|
||||||
}`,
|
}. Got ${data.byteLength}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const assetProxyId = ethUtil.bufferToHex(data.slice(0, constants.SELECTOR_LENGTH));
|
||||||
const encodedTokenAddress = encodedAssetData.slice(
|
|
||||||
ASSET_DATA_ADDRESS_OFFSET,
|
|
||||||
ERC721_ASSET_DATA_TOKEN_ID_OFFSET,
|
|
||||||
);
|
|
||||||
const proxyIdOffset = encodedAssetData.byteLength - 1;
|
|
||||||
const encodedAssetProxyId = encodedAssetData.slice(proxyIdOffset);
|
|
||||||
const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId);
|
|
||||||
if (assetProxyId !== AssetProxyId.ERC721) {
|
if (assetProxyId !== AssetProxyId.ERC721) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Could not decode ERC721 Proxy Data. Expected Asset Proxy Id to be ERC721 (${
|
`Could not decode ERC721 Asset Data. Expected Asset Proxy Id to be ERC721 (${
|
||||||
AssetProxyId.ERC721
|
AssetProxyId.ERC721
|
||||||
}), but got ${assetProxyId}`,
|
}), but got ${assetProxyId}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const tokenAddress = assetProxyUtils.decodeAddress(encodedTokenAddress);
|
const [tokenAddress, tokenId, receiverData] = ethAbi.rawDecode(
|
||||||
const encodedTokenId = encodedAssetData.slice(
|
['address', 'uint256', 'bytes'],
|
||||||
ERC721_ASSET_DATA_TOKEN_ID_OFFSET,
|
data.slice(constants.SELECTOR_LENGTH),
|
||||||
ERC721_ASSET_DATA_RECEIVER_DATA_LENGTH_OFFSET,
|
|
||||||
);
|
);
|
||||||
const tokenId = assetProxyUtils.decodeUint256(encodedTokenId);
|
return {
|
||||||
let receiverData = NULL_BYTES;
|
|
||||||
const lengthUpToReceiverDataLength = ERC721_ASSET_DATA_RECEIVER_DATA_LENGTH_OFFSET + 1;
|
|
||||||
if (encodedAssetData.byteLength > lengthUpToReceiverDataLength) {
|
|
||||||
const encodedReceiverDataLength = encodedAssetData.slice(
|
|
||||||
ERC721_ASSET_DATA_RECEIVER_DATA_LENGTH_OFFSET,
|
|
||||||
ERC721_ASSET_DATA_RECEIVER_DATA_OFFSET,
|
|
||||||
);
|
|
||||||
const receiverDataLength = assetProxyUtils.decodeUint256(encodedReceiverDataLength);
|
|
||||||
const lengthUpToReceiverData = ERC721_ASSET_DATA_RECEIVER_DATA_OFFSET + 1;
|
|
||||||
const expectedReceiverDataLength = new BigNumber(encodedAssetData.byteLength - lengthUpToReceiverData);
|
|
||||||
if (!receiverDataLength.equals(expectedReceiverDataLength)) {
|
|
||||||
throw new Error(
|
|
||||||
`Data length (${receiverDataLength}) does not match actual length of data (${expectedReceiverDataLength})`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const encodedReceiverData = encodedAssetData.slice(
|
|
||||||
ERC721_ASSET_DATA_RECEIVER_DATA_OFFSET,
|
|
||||||
receiverDataLength.add(ERC721_ASSET_DATA_RECEIVER_DATA_OFFSET).toNumber(),
|
|
||||||
);
|
|
||||||
receiverData = ethUtil.bufferToHex(encodedReceiverData);
|
|
||||||
}
|
|
||||||
const erc721AssetData: ERC721AssetData = {
|
|
||||||
assetProxyId,
|
assetProxyId,
|
||||||
tokenAddress,
|
tokenAddress: ethUtil.addHexPrefix(tokenAddress),
|
||||||
tokenId,
|
tokenId: new BigNumber(tokenId.toString()),
|
||||||
receiverData,
|
receiverData: ethUtil.bufferToHex(receiverData),
|
||||||
};
|
};
|
||||||
return erc721AssetData;
|
|
||||||
},
|
},
|
||||||
decodeAssetDataId(assetData: string): AssetProxyId {
|
decodeAssetDataId(assetData: string): AssetProxyId {
|
||||||
const encodedAssetData = ethUtil.toBuffer(assetData);
|
const encodedAssetData = ethUtil.toBuffer(assetData);
|
||||||
if (encodedAssetData.byteLength < 1) {
|
if (encodedAssetData.byteLength < constants.SELECTOR_LENGTH) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Could not decode Proxy Data. Expected length of encoded data to be at least 1. Got ${
|
`Could not decode Proxy Data. Expected length of encoded data to be at least 4. Got ${
|
||||||
encodedAssetData.byteLength
|
encodedAssetData.byteLength
|
||||||
}`,
|
}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const encodedAssetProxyId = encodedAssetData.slice(-1);
|
const encodedAssetProxyId = encodedAssetData.slice(0, constants.SELECTOR_LENGTH);
|
||||||
const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId);
|
const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId);
|
||||||
return assetProxyId;
|
return assetProxyId;
|
||||||
},
|
},
|
||||||
|
@@ -5,4 +5,10 @@ export const constants = {
|
|||||||
// tslint:disable-next-line:custom-no-magic-numbers
|
// tslint:disable-next-line:custom-no-magic-numbers
|
||||||
UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1),
|
UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1),
|
||||||
TESTRPC_NETWORK_ID: 50,
|
TESTRPC_NETWORK_ID: 50,
|
||||||
|
ADDRESS_LENGTH: 20,
|
||||||
|
WORD_LENGTH: 32,
|
||||||
|
ERC20_ASSET_DATA_BYTE_LENGTH: 36,
|
||||||
|
ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH: 53,
|
||||||
|
SELECTOR_LENGTH: 4,
|
||||||
|
BASE_16: 16,
|
||||||
};
|
};
|
||||||
|
@@ -152,18 +152,18 @@ export interface ECSignature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum AssetProxyId {
|
export enum AssetProxyId {
|
||||||
INVALID,
|
INVALID = '0x00000000',
|
||||||
ERC20,
|
ERC20 = '0xf47261b0',
|
||||||
ERC721,
|
ERC721 = '0x08e937fa',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ERC20AssetData {
|
export interface ERC20AssetData {
|
||||||
assetProxyId: AssetProxyId;
|
assetProxyId: string;
|
||||||
tokenAddress: string;
|
tokenAddress: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ERC721AssetData {
|
export interface ERC721AssetData {
|
||||||
assetProxyId: AssetProxyId;
|
assetProxyId: string;
|
||||||
tokenAddress: string;
|
tokenAddress: string;
|
||||||
tokenId: BigNumber;
|
tokenId: BigNumber;
|
||||||
receiverData: string;
|
receiverData: string;
|
||||||
|
Reference in New Issue
Block a user