Pop id from assetData before dispatching to AssetProxies

This commit is contained in:
Amir Bandeali
2018-06-09 19:01:28 -07:00
parent 787015f537
commit ee8c9b764d
20 changed files with 269 additions and 210 deletions

View File

@@ -47,16 +47,7 @@ contract ERC20Proxy is
internal
{
// Decode asset data.
(
uint8 proxyId,
address token
) = decodeERC20AssetData(assetData);
// Data must be intended for this proxy.
require(
proxyId == PROXY_ID,
ASSET_PROXY_ID_MISMATCH
);
address token = readAddress(assetData, 0);
// Transfer tokens.
bool success = IERC20Token(token).transferFrom(from, to, amount);
@@ -75,30 +66,4 @@ contract ERC20Proxy is
{
return PROXY_ID;
}
/// @dev Decodes ERC20 Asset data.
/// @param assetData Encoded byte array.
/// @return proxyId Intended ERC20 proxy id.
/// @return token ERC20 token address.
function decodeERC20AssetData(bytes memory assetData)
internal
pure
returns (
uint8 proxyId,
address token
)
{
// Validate encoded data length
uint256 length = assetData.length;
require(
length == 21,
LENGTH_21_REQUIRED
);
// Decode data
token = readAddress(assetData, 0);
proxyId = uint8(assetData[length - 1]);
return (proxyId, token);
}
}

View File

@@ -46,30 +46,22 @@ contract ERC721Proxy is
)
internal
{
// Decode asset data.
(
uint8 proxyId,
address token,
uint256 tokenId,
bytes memory receiverData
) = decodeERC721AssetData(assetData);
// Data must be intended for this proxy.
require(
proxyId == PROXY_ID,
ASSET_PROXY_ID_MISMATCH
);
// There exists only 1 of each token.
require(
amount == 1,
INVALID_AMOUNT
);
// Decode asset data.
(
address token,
uint256 tokenId,
bytes memory receiverData
) = decodeERC721AssetData(assetData);
// Transfer token. Saves gas by calling safeTransferFrom only
// when there is receiverData present. Either succeeds or throws.
if(receiverData.length > 0) {
if (receiverData.length > 0) {
ERC721Token(token).safeTransferFrom(from, to, tokenId, receiverData);
} else {
ERC721Token(token).transferFrom(from, to, tokenId);
@@ -97,29 +89,19 @@ contract ERC721Proxy is
internal
pure
returns (
uint8 proxyId,
address token,
uint256 tokenId,
bytes memory receiverData
)
{
// Validate encoded data length
uint256 length = assetData.length;
require(
length >= 53,
LENGTH_AT_LEAST_53_REQUIRED
);
// Decode asset data.
token = readAddress(assetData, 0);
tokenId = readUint256(assetData, 20);
if (length > 53) {
if (assetData.length > 52) {
receiverData = readBytes(assetData, 52);
}
proxyId = uint8(assetData[length - 1]);
return (
proxyId,
token,
tokenId,
receiverData

View File

@@ -27,11 +27,6 @@ contract LibAssetProxyErrors {
string constant AUTHORIZED_ADDRESS_MISMATCH = "AUTHORIZED_ADDRESS_MISMATCH"; // Address at index does not match given target address.
/// AssetProxy errors ///
string constant ASSET_PROXY_ID_MISMATCH = "ASSET_PROXY_ID_MISMATCH"; // Proxy id in metadata does not match this proxy id.
string constant INVALID_AMOUNT = "INVALID_AMOUNT"; // Transfer amount must equal 1.
string constant TRANSFER_FAILED = "TRANSFER_FAILED"; // Transfer failed.
/// Length validation errors ///
string constant LENGTH_21_REQUIRED = "LENGTH_21_REQUIRED"; // Byte array must have a length of 21.
string constant LENGTH_AT_LEAST_53_REQUIRED = "LENGTH_AT_LEAST_53_REQUIRED"; // Byte array must have a length of at least 53.
}

View File

@@ -19,12 +19,14 @@
pragma solidity ^0.4.24;
import "../../utils/Ownable/Ownable.sol";
import "../../utils/LibBytes/LibBytes.sol";
import "./libs/LibExchangeErrors.sol";
import "./mixins/MAssetProxyDispatcher.sol";
import "../AssetProxy/interfaces/IAssetProxy.sol";
contract MixinAssetProxyDispatcher is
Ownable,
LibBytes,
LibExchangeErrors,
MAssetProxyDispatcher
{
@@ -81,11 +83,13 @@ contract MixinAssetProxyDispatcher is
/// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws.
/// @param assetData Byte array encoded for the respective asset proxy.
/// @param assetProxyId Id of assetProxy to dispach to.
/// @param from Address to transfer token from.
/// @param to Address to transfer token to.
/// @param amount Amount of token to transfer.
function dispatchTransferFrom(
bytes memory assetData,
uint8 assetProxyId,
address from,
address to,
uint256 amount
@@ -94,16 +98,8 @@ contract MixinAssetProxyDispatcher is
{
// Do nothing if no amount should be transferred.
if (amount > 0) {
// Lookup asset proxy
uint256 length = assetData.length;
require(
length > 0,
LENGTH_GREATER_THAN_0_REQUIRED
);
uint8 assetProxyId = uint8(assetData[length - 1]);
// Lookup assetProxy
IAssetProxy assetProxy = assetProxies[assetProxyId];
// transferFrom will either succeed or throw.
assetProxy.transferFrom(assetData, from, to, amount);
}

View File

@@ -108,9 +108,6 @@ contract MixinExchangeCore is
// Compute proportional fill amounts
fillResults = calculateFillResults(order, takerAssetFilledAmount);
// Settle order
settleOrder(order, takerAddress, fillResults);
// Update exchange internal state
updateFilledState(
order,
@@ -119,6 +116,10 @@ contract MixinExchangeCore is
orderInfo.orderTakerAssetFilledAmount,
fillResults
);
// Settle order
settleOrder(order, takerAddress, fillResults);
return fillResults;
}

View File

@@ -14,7 +14,6 @@
pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;
import "../../utils/LibBytes/LibBytes.sol";
import "./libs/LibMath.sol";
import "./libs/LibOrder.sol";
import "./libs/LibFillResults.sol";
@@ -25,7 +24,6 @@ import "./mixins/MSettlement.sol";
import "./mixins/MTransactions.sol";
contract MixinMatchOrders is
LibBytes,
LibMath,
LibExchangeErrors,
MExchangeCore,
@@ -94,14 +92,6 @@ contract MixinMatchOrders is
rightSignature
);
// Settle matched orders. Succeeds or throws.
settleMatchedOrders(
leftOrder,
rightOrder,
takerAddress,
matchedFillResults
);
// Update exchange state
updateFilledState(
leftOrder,
@@ -117,6 +107,14 @@ contract MixinMatchOrders is
rightOrderInfo.orderTakerAssetFilledAmount,
matchedFillResults.right
);
// Settle matched orders. Succeeds or throws.
settleMatchedOrders(
leftOrder,
rightOrder,
takerAddress,
matchedFillResults
);
return matchedFillResults;
}

View File

@@ -19,6 +19,7 @@
pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;
import "../../utils/LibBytes/LibBytes.sol";
import "./libs/LibMath.sol";
import "./libs/LibFillResults.sol";
import "./libs/LibOrder.sol";
@@ -28,6 +29,7 @@ import "./mixins/MSettlement.sol";
import "./mixins/MAssetProxyDispatcher.sol";
contract MixinSettlement is
LibBytes,
LibMath,
LibExchangeErrors,
MMatchOrders,
@@ -37,7 +39,7 @@ contract MixinSettlement is
// ZRX metadata used for fee transfers.
// This will be constant throughout the life of the Exchange contract,
// since ZRX will always be transferred via the ERC20 AssetProxy.
bytes internal ZRX_PROXY_DATA;
bytes internal ZRX_ASSET_DATA;
/// @dev Gets the ZRX metadata used for fee transfers.
function zrxAssetData()
@@ -45,7 +47,7 @@ contract MixinSettlement is
view
returns (bytes memory)
{
return ZRX_PROXY_DATA;
return ZRX_ASSET_DATA;
}
/// TODO: _zrxAssetData should be a constant in production.
@@ -54,7 +56,7 @@ contract MixinSettlement is
constructor (bytes memory _zrxAssetData)
public
{
ZRX_PROXY_DATA = _zrxAssetData;
ZRX_ASSET_DATA = _zrxAssetData;
}
/// @dev Settles an order by transferring assets between counterparties.
@@ -68,26 +70,34 @@ contract MixinSettlement is
)
internal
{
uint8 makerAssetProxyId = uint8(popByte(order.makerAssetData));
uint8 takerAssetProxyId = uint8(popByte(order.takerAssetData));
bytes memory zrxAssetData = ZRX_ASSET_DATA;
uint8 zrxProxyId = uint8(popByte(zrxAssetData));
dispatchTransferFrom(
order.makerAssetData,
makerAssetProxyId,
order.makerAddress,
takerAddress,
fillResults.makerAssetFilledAmount
);
dispatchTransferFrom(
order.takerAssetData,
takerAssetProxyId,
takerAddress,
order.makerAddress,
fillResults.takerAssetFilledAmount
);
dispatchTransferFrom(
ZRX_PROXY_DATA,
zrxAssetData,
zrxProxyId,
order.makerAddress,
order.feeRecipientAddress,
fillResults.makerFeePaid
);
dispatchTransferFrom(
ZRX_PROXY_DATA,
zrxAssetData,
zrxProxyId,
takerAddress,
order.feeRecipientAddress,
fillResults.takerFeePaid
@@ -107,21 +117,28 @@ contract MixinSettlement is
)
internal
{
uint8 leftMakerAssetProxyId = uint8(popByte(leftOrder.makerAssetData));
uint8 rightMakerAssetProxyId = uint8(popByte(rightOrder.makerAssetData));
bytes memory zrxAssetData = ZRX_ASSET_DATA;
uint8 zrxProxyId = uint8(popByte(zrxAssetData));
// Order makers and taker
dispatchTransferFrom(
leftOrder.makerAssetData,
leftMakerAssetProxyId,
leftOrder.makerAddress,
rightOrder.makerAddress,
matchedFillResults.right.takerAssetFilledAmount
);
dispatchTransferFrom(
rightOrder.makerAssetData,
rightMakerAssetProxyId,
rightOrder.makerAddress,
leftOrder.makerAddress,
matchedFillResults.left.takerAssetFilledAmount
);
dispatchTransferFrom(
leftOrder.makerAssetData,
leftMakerAssetProxyId,
leftOrder.makerAddress,
takerAddress,
matchedFillResults.leftMakerAssetSpreadAmount
@@ -129,13 +146,15 @@ contract MixinSettlement is
// Maker fees
dispatchTransferFrom(
ZRX_PROXY_DATA,
zrxAssetData,
zrxProxyId,
leftOrder.makerAddress,
leftOrder.feeRecipientAddress,
matchedFillResults.left.makerFeePaid
);
dispatchTransferFrom(
ZRX_PROXY_DATA,
zrxAssetData,
zrxProxyId,
rightOrder.makerAddress,
rightOrder.feeRecipientAddress,
matchedFillResults.right.makerFeePaid
@@ -144,7 +163,8 @@ contract MixinSettlement is
// Taker fees
if (leftOrder.feeRecipientAddress == rightOrder.feeRecipientAddress) {
dispatchTransferFrom(
ZRX_PROXY_DATA,
zrxAssetData,
zrxProxyId,
takerAddress,
leftOrder.feeRecipientAddress,
safeAdd(
@@ -154,13 +174,15 @@ contract MixinSettlement is
);
} else {
dispatchTransferFrom(
ZRX_PROXY_DATA,
zrxAssetData,
zrxProxyId,
takerAddress,
leftOrder.feeRecipientAddress,
matchedFillResults.left.takerFeePaid
);
dispatchTransferFrom(
ZRX_PROXY_DATA,
zrxAssetData,
zrxProxyId,
takerAddress,
rightOrder.feeRecipientAddress,
matchedFillResults.right.takerFeePaid

View File

@@ -335,12 +335,13 @@ contract MixinWrapperFunctions is
{
for (uint256 i = 0; i < orders.length; i++) {
// Token being sold by taker must be the same for each order
// TODO: optimize by only using takerAssetData for first order.
require(
areBytesEqual(orders[i].takerAssetData, orders[0].takerAssetData),
ASSET_DATA_MISMATCH
);
// We assume that asset being sold by taker is the same for each order.
// Rather than passing this in as calldata, we copy the takerAssetData from the first order onto all later orders.
// We cannot reference the same takerAssetData byte array because the array is modified when a trade is settled.
uint256 next = i + 1;
if (next != orders.length) {
deepCopyBytes(orders[next].takerAssetData, orders[i].takerAssetData);
}
// Calculate the remaining amount of takerAsset to sell
uint256 remainingTakerAssetFillAmount = safeSub(takerAssetFillAmount, totalFillResults.takerAssetFilledAmount);
@@ -379,12 +380,13 @@ contract MixinWrapperFunctions is
{
for (uint256 i = 0; i < orders.length; i++) {
// Token being sold by taker must be the same for each order
// TODO: optimize by only using takerAssetData for first order.
require(
areBytesEqual(orders[i].takerAssetData, orders[0].takerAssetData),
ASSET_DATA_MISMATCH
);
// We assume that asset being sold by taker is the same for each order.
// Rather than passing this in as calldata, we copy the takerAssetData from the first order onto all later orders.
// We cannot reference the same takerAssetData byte array because the array is modified when a trade is settled.
uint256 next = i + 1;
if (next != orders.length) {
deepCopyBytes(orders[next].takerAssetData, orders[i].takerAssetData);
}
// Calculate the remaining amount of takerAsset to sell
uint256 remainingTakerAssetFillAmount = safeSub(takerAssetFillAmount, totalFillResults.takerAssetFilledAmount);
@@ -422,12 +424,13 @@ contract MixinWrapperFunctions is
{
for (uint256 i = 0; i < orders.length; i++) {
// Token being bought by taker must be the same for each order
// TODO: optimize by only using makerAssetData for first order.
require(
areBytesEqual(orders[i].makerAssetData, orders[0].makerAssetData),
ASSET_DATA_MISMATCH
);
// We assume that asset being bought by taker is the same for each order.
// Rather than passing this in as calldata, we copy the makerAssetData from the first order onto all later orders.
// We cannot reference the same makerAssetData byte array because the array is modified when a trade is settled.
uint256 next = i + 1;
if (next != orders.length) {
deepCopyBytes(orders[next].makerAssetData, orders[i].makerAssetData);
}
// Calculate the remaining amount of makerAsset to buy
uint256 remainingMakerAssetFillAmount = safeSub(makerAssetFillAmount, totalFillResults.makerAssetFilledAmount);
@@ -474,12 +477,13 @@ contract MixinWrapperFunctions is
{
for (uint256 i = 0; i < orders.length; i++) {
// Token being bought by taker must be the same for each order
// TODO: optimize by only using makerAssetData for first order.
require(
areBytesEqual(orders[i].makerAssetData, orders[0].makerAssetData),
ASSET_DATA_MISMATCH
);
// We assume that asset being bought by taker is the same for each order.
// Rather than passing this in as calldata, we copy the makerAssetData from the first order onto all later orders.
// We cannot reference the same makerAssetData byte array because the array is modified when a trade is settled.
uint256 next = i + 1;
if (next != orders.length) {
deepCopyBytes(orders[next].makerAssetData, orders[i].makerAssetData);
}
// Calculate the remaining amount of makerAsset to buy
uint256 remainingMakerAssetFillAmount = safeSub(makerAssetFillAmount, totalFillResults.makerAssetFilledAmount);

View File

@@ -25,7 +25,6 @@ contract LibExchangeErrors {
string constant INVALID_TAKER = "INVALID_TAKER"; // Invalid takerAddress.
string constant INVALID_SENDER = "INVALID_SENDER"; // Invalid `msg.sender`.
string constant INVALID_ORDER_SIGNATURE = "INVALID_ORDER_SIGNATURE"; // Signature validation failed.
string constant ASSET_DATA_MISMATCH = "ASSET_DATA_MISMATCH"; // Asset data must be the same for each order.
/// fillOrder validation errors ///
string constant INVALID_TAKER_AMOUNT = "INVALID_TAKER_AMOUNT"; // takerAssetFillAmount cannot equal 0.

View File

@@ -34,11 +34,13 @@ contract MAssetProxyDispatcher is
/// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws.
/// @param assetData Byte array encoded for the respective asset proxy.
/// @param assetProxyId Id of assetProxy to dispach to.
/// @param from Address to transfer token from.
/// @param to Address to transfer token to.
/// @param amount Amount of token to transfer.
function dispatchTransferFrom(
bytes memory assetData,
uint8 assetProxyId,
address from,
address to,
uint256 amount

View File

@@ -23,26 +23,8 @@ import "../../protocol/AssetProxy/ERC20Proxy.sol";
import "../../protocol/AssetProxy/ERC721Proxy.sol";
contract TestAssetDataDecoders is
ERC20Proxy,
ERC721Proxy
{
/// @dev Decodes ERC20 Asset data.
/// @param assetData Encoded byte array.
/// @return proxyId Intended ERC20 proxy id.
/// @return token ERC20 token address.
function publicDecodeERC20Data(bytes memory assetData)
public
pure
returns (
uint8 proxyId,
address token
)
{
(proxyId, token) = decodeERC20AssetData(assetData);
return (proxyId, token);
}
/// @dev Decodes ERC721 Asset data.
/// @param assetData Encoded byte array.
/// @return proxyId Intended ERC721 proxy id.
@@ -54,21 +36,18 @@ contract TestAssetDataDecoders is
public
pure
returns (
uint8 proxyId,
address token,
uint256 tokenId,
bytes memory receiverData
)
{
(
proxyId,
token,
tokenId,
receiverData
) = decodeERC721AssetData(assetData);
return (
proxyId,
token,
tokenId,
receiverData

View File

@@ -24,11 +24,12 @@ import "../../protocol/Exchange/MixinAssetProxyDispatcher.sol";
contract TestAssetProxyDispatcher is MixinAssetProxyDispatcher {
function publicDispatchTransferFrom(
bytes memory assetData,
uint8 assetProxyId,
address from,
address to,
uint256 amount)
public
{
dispatchTransferFrom(assetData, from, to, amount);
dispatchTransferFrom(assetData, assetProxyId, from, to, amount);
}
}

View File

@@ -30,6 +30,7 @@ contract LibBytes is
string constant GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED";
string constant GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED";
string constant GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED";
string constant GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED";
/// @dev Pops the last byte off of a byte array by modifying its length.
/// @param b Byte array that will be modified.
@@ -114,6 +115,29 @@ contract LibBytes is
return equal;
}
/// @dev Performs a deep copy of a byte array onto another byte array of greater than or equal length.
/// @param dest Byte array that will be overwritten with source bytes.
/// @param source Byte array to copy onto dest bytes.
function deepCopyBytes(
bytes memory dest,
bytes memory source
)
internal
pure
{
uint256 sourceLen = source.length;
// Dest length must be >= source length, or some bytes would not be copied.
require(
dest.length >= sourceLen,
GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED
);
memCopy(
getMemAddress(dest) + 32, // +32 to skip length of <dest>
getMemAddress(source) + 32, // +32 to skip length of <source>
sourceLen
);
}
/// @dev Reads an address from a position in a byte array.
/// @param b Byte array containing an address.
/// @param index Index in byte array of address.

View File

@@ -165,7 +165,7 @@ export class ExchangeWrapper {
public async marketBuyOrdersNoThrowAsync(
orders: SignedOrder[],
from: string,
opts: { makerAssetFillAmount: BigNumber },
opts: { makerAssetFillAmount: BigNumber; gas?: number },
): Promise<TransactionReceiptWithDecodedLogs> {
const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount);
const txHash = await this._exchange.marketBuyOrdersNoThrow.sendTransactionAsync(

View File

@@ -28,8 +28,14 @@ export const formatters = {
signatures: [],
takerAssetFillAmount,
};
_.forEach(signedOrders, signedOrder => {
_.forEach(signedOrders, (signedOrder, i) => {
const orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder);
if (i !== 0) {
orderWithoutExchangeAddress.takerAssetData = `0x${_.repeat(
'0',
signedOrders[0].takerAssetData.length - 2,
)}`;
}
marketSellOrders.orders.push(orderWithoutExchangeAddress);
marketSellOrders.signatures.push(signedOrder.signature);
});
@@ -41,8 +47,14 @@ export const formatters = {
signatures: [],
makerAssetFillAmount,
};
_.forEach(signedOrders, signedOrder => {
_.forEach(signedOrders, (signedOrder, i) => {
const orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder);
if (i !== 0) {
orderWithoutExchangeAddress.makerAssetData = `0x${_.repeat(
'0',
signedOrders[0].makerAssetData.length - 2,
)}`;
}
marketBuyOrders.orders.push(orderWithoutExchangeAddress);
marketBuyOrders.signatures.push(signedOrder.signature);
});

View File

@@ -1,6 +1,7 @@
import { OrderWithoutExchangeAddress, SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import { constants } from './constants';
import { CancelOrder, MatchOrder } from './types';
export const orderUtils = {
@@ -43,6 +44,8 @@ export const orderUtils = {
leftSignature: signedOrderLeft.signature,
rightSignature: signedOrderRight.signature,
};
fill.right.makerAssetData = constants.NULL_BYTES;
fill.right.takerAssetData = constants.NULL_BYTES;
return fill;
},
};

View File

@@ -42,33 +42,19 @@ describe('TestAssetDataDecoders', () => {
});
describe('Asset Data Decoders', () => {
it('should correctly decode ERC20 asset data)', async () => {
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(testAddress);
const expectedDecodedAssetData = assetProxyUtils.decodeERC20AssetData(encodedAssetData);
let decodedAssetProxyId: number;
let decodedTokenAddress: string;
[decodedAssetProxyId, decodedTokenAddress] = await testAssetProxyDecoder.publicDecodeERC20Data.callAsync(
encodedAssetData,
);
expect(decodedAssetProxyId).to.be.equal(expectedDecodedAssetData.assetProxyId);
expect(decodedTokenAddress).to.be.equal(expectedDecodedAssetData.tokenAddress);
});
it('should correctly decode ERC721 asset data', async () => {
const tokenId = generatePseudoRandomSalt();
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(testAddress, tokenId);
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
const expectedDecodedAssetData = assetProxyUtils.decodeERC721AssetData(encodedAssetData);
let decodedAssetProxyId: number;
let decodedTokenAddress: string;
let decodedTokenId: BigNumber;
let decodedData: string;
[
decodedAssetProxyId,
decodedTokenAddress,
decodedTokenId,
decodedData,
] = await testAssetProxyDecoder.publicDecodeERC721Data.callAsync(encodedAssetData);
expect(decodedAssetProxyId).to.be.equal(expectedDecodedAssetData.assetProxyId);
] = await testAssetProxyDecoder.publicDecodeERC721Data.callAsync(encodedAssetDataWithoutProxyId);
expect(decodedTokenAddress).to.be.equal(expectedDecodedAssetData.tokenAddress);
expect(decodedTokenId).to.be.bignumber.equal(expectedDecodedAssetData.tokenId);
expect(decodedData).to.be.equal(expectedDecodedAssetData.receiverData);
@@ -84,17 +70,14 @@ describe('TestAssetDataDecoders', () => {
const receiverData = receiverDataFirst32Bytes + receiverDataExtraBytes;
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(testAddress, tokenId, receiverData);
const expectedDecodedAssetData = assetProxyUtils.decodeERC721AssetData(encodedAssetData);
let decodedAssetProxyId: number;
let decodedTokenAddress: string;
let decodedTokenId: BigNumber;
let decodedReceiverData: string;
[
decodedAssetProxyId,
decodedTokenAddress,
decodedTokenId,
decodedReceiverData,
] = await testAssetProxyDecoder.publicDecodeERC721Data.callAsync(encodedAssetData);
expect(decodedAssetProxyId).to.be.equal(expectedDecodedAssetData.assetProxyId);
expect(decodedTokenAddress).to.be.equal(expectedDecodedAssetData.tokenAddress);
expect(decodedTokenId).to.be.bignumber.equal(expectedDecodedAssetData.tokenId);
expect(decodedReceiverData).to.be.equal(expectedDecodedAssetData.receiverData);

View File

@@ -96,12 +96,13 @@ describe('Asset Transfer Proxies', () => {
it('should successfully transfer tokens', async () => {
// Construct ERC20 asset data
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// Perform a transfer from makerAddress to takerAddress
const erc20Balances = await erc20Wrapper.getBalancesAsync();
const amount = new BigNumber(10);
await web3Wrapper.awaitTransactionSuccessAsync(
await erc20Proxy.transferFrom.sendTransactionAsync(
encodedAssetData,
encodedAssetDataWithoutProxyId,
makerAddress,
takerAddress,
amount,
@@ -122,12 +123,13 @@ describe('Asset Transfer Proxies', () => {
it('should do nothing if transferring 0 amount of a token', async () => {
// Construct ERC20 asset data
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// Perform a transfer from makerAddress to takerAddress
const erc20Balances = await erc20Wrapper.getBalancesAsync();
const amount = new BigNumber(0);
await web3Wrapper.awaitTransactionSuccessAsync(
await erc20Proxy.transferFrom.sendTransactionAsync(
encodedAssetData,
encodedAssetDataWithoutProxyId,
makerAddress,
takerAddress,
amount,
@@ -172,12 +174,19 @@ describe('Asset Transfer Proxies', () => {
it('should throw if requesting address is not authorized', async () => {
// Construct ERC20 asset data
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// Perform a transfer from makerAddress to takerAddress
const amount = new BigNumber(10);
return expectRevertOrAlwaysFailingTransactionAsync(
erc20Proxy.transferFrom.sendTransactionAsync(encodedAssetData, makerAddress, takerAddress, amount, {
from: notAuthorized,
}),
erc20Proxy.transferFrom.sendTransactionAsync(
encodedAssetDataWithoutProxyId,
makerAddress,
takerAddress,
amount,
{
from: notAuthorized,
},
),
);
});
});
@@ -187,9 +196,10 @@ describe('Asset Transfer Proxies', () => {
const erc20Balances = await erc20Wrapper.getBalancesAsync();
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
const amount = new BigNumber(10);
const numTransfers = 2;
const assetData = _.times(numTransfers, () => encodedAssetData);
const assetData = _.times(numTransfers, () => encodedAssetDataWithoutProxyId);
const fromAddresses = _.times(numTransfers, () => makerAddress);
const toAddresses = _.times(numTransfers, () => takerAddress);
const amounts = _.times(numTransfers, () => amount);
@@ -218,9 +228,10 @@ describe('Asset Transfer Proxies', () => {
it('should throw if not called by an authorized address', async () => {
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
const amount = new BigNumber(10);
const numTransfers = 2;
const assetData = _.times(numTransfers, () => encodedAssetData);
const assetData = _.times(numTransfers, () => encodedAssetDataWithoutProxyId);
const fromAddresses = _.times(numTransfers, () => makerAddress);
const toAddresses = _.times(numTransfers, () => takerAddress);
const amounts = _.times(numTransfers, () => amount);
@@ -244,6 +255,7 @@ describe('Asset Transfer Proxies', () => {
it('should successfully transfer tokens', async () => {
// Construct ERC721 asset data
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// Verify pre-condition
const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
@@ -251,7 +263,7 @@ describe('Asset Transfer Proxies', () => {
const amount = new BigNumber(1);
await web3Wrapper.awaitTransactionSuccessAsync(
await erc721Proxy.transferFrom.sendTransactionAsync(
encodedAssetData,
encodedAssetDataWithoutProxyId,
makerAddress,
takerAddress,
amount,
@@ -267,13 +279,14 @@ describe('Asset Transfer Proxies', () => {
it('should not call onERC721Received when transferring to a smart contract without receiver data', async () => {
// Construct ERC721 asset data
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// Verify pre-condition
const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
// Perform a transfer from makerAddress to takerAddress
const amount = new BigNumber(1);
const txHash = await erc721Proxy.transferFrom.sendTransactionAsync(
encodedAssetData,
encodedAssetDataWithoutProxyId,
makerAddress,
erc721Receiver.address,
amount,
@@ -298,13 +311,14 @@ describe('Asset Transfer Proxies', () => {
erc721MakerTokenId,
receiverData,
);
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// Verify pre-condition
const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
// Perform a transfer from makerAddress to takerAddress
const amount = new BigNumber(1);
const txHash = await erc721Proxy.transferFrom.sendTransactionAsync(
encodedAssetData,
encodedAssetDataWithoutProxyId,
makerAddress,
erc721Receiver.address,
amount,
@@ -333,6 +347,7 @@ describe('Asset Transfer Proxies', () => {
erc721MakerTokenId,
receiverData,
);
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// Verify pre-condition
const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
@@ -340,7 +355,7 @@ describe('Asset Transfer Proxies', () => {
const amount = new BigNumber(1);
return expectRevertOrAlwaysFailingTransactionAsync(
erc721Proxy.transferFrom.sendTransactionAsync(
encodedAssetData,
encodedAssetDataWithoutProxyId,
makerAddress,
erc20Proxy.address, // the ERC20 proxy does not have an ERC721 receiver
amount,
@@ -352,6 +367,7 @@ describe('Asset Transfer Proxies', () => {
it('should throw if transferring 0 amount of a token', async () => {
// Construct ERC721 asset data
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// Verify pre-condition
const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
@@ -359,7 +375,7 @@ describe('Asset Transfer Proxies', () => {
const amount = new BigNumber(0);
return expectRevertOrAlwaysFailingTransactionAsync(
erc721Proxy.transferFrom.sendTransactionAsync(
encodedAssetData,
encodedAssetDataWithoutProxyId,
makerAddress,
takerAddress,
amount,
@@ -371,6 +387,7 @@ describe('Asset Transfer Proxies', () => {
it('should throw if transferring > 1 amount of a token', async () => {
// Construct ERC721 asset data
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// Verify pre-condition
const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
@@ -378,7 +395,7 @@ describe('Asset Transfer Proxies', () => {
const amount = new BigNumber(500);
return expectRevertOrAlwaysFailingTransactionAsync(
erc721Proxy.transferFrom.sendTransactionAsync(
encodedAssetData,
encodedAssetDataWithoutProxyId,
makerAddress,
takerAddress,
amount,
@@ -390,6 +407,7 @@ describe('Asset Transfer Proxies', () => {
it('should throw if allowances are too low', async () => {
// Construct ERC721 asset data
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// Remove transfer approval for makerAddress.
await web3Wrapper.awaitTransactionSuccessAsync(
await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, false, {
@@ -400,20 +418,27 @@ describe('Asset Transfer Proxies', () => {
// Perform a transfer; expect this to fail.
const amount = new BigNumber(1);
return expectRevertOrAlwaysFailingTransactionAsync(
erc20Proxy.transferFrom.sendTransactionAsync(encodedAssetData, makerAddress, takerAddress, amount, {
from: notAuthorized,
}),
erc20Proxy.transferFrom.sendTransactionAsync(
encodedAssetDataWithoutProxyId,
makerAddress,
takerAddress,
amount,
{
from: notAuthorized,
},
),
);
});
it('should throw if requesting address is not authorized', async () => {
// Construct ERC721 asset data
const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// Perform a transfer from makerAddress to takerAddress
const amount = new BigNumber(1);
return expectRevertOrAlwaysFailingTransactionAsync(
erc721Proxy.transferFrom.sendTransactionAsync(
encodedAssetData,
encodedAssetDataWithoutProxyId,
makerAddress,
takerAddress,
amount,
@@ -430,8 +455,8 @@ describe('Asset Transfer Proxies', () => {
const numTransfers = 2;
const assetData = [
assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdA),
assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdB),
assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdA).slice(0, -2),
assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdB).slice(0, -2),
];
const fromAddresses = _.times(numTransfers, () => makerAddress);
const toAddresses = _.times(numTransfers, () => takerAddress);
@@ -462,8 +487,8 @@ describe('Asset Transfer Proxies', () => {
const numTransfers = 2;
const assetData = [
assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdA),
assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdB),
assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdA).slice(0, -2),
assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdB).slice(0, -2),
];
const fromAddresses = _.times(numTransfers, () => makerAddress);
const toAddresses = _.times(numTransfers, () => takerAddress);
@@ -484,3 +509,4 @@ describe('Asset Transfer Proxies', () => {
});
});
// tslint:enable:no-unnecessary-type-assertion
// tslint:disable:max-file-line-count

View File

@@ -276,12 +276,14 @@ describe('AssetProxyDispatcher', () => {
);
// Construct metadata for ERC20 proxy
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// Perform a transfer from makerAddress to takerAddress
const erc20Balances = await erc20Wrapper.getBalancesAsync();
const amount = new BigNumber(10);
await web3Wrapper.awaitTransactionSuccessAsync(
await assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync(
encodedAssetData,
encodedAssetDataWithoutProxyId,
AssetProxyId.ERC20,
makerAddress,
takerAddress,
amount,
@@ -302,11 +304,13 @@ describe('AssetProxyDispatcher', () => {
it('should throw if dispatching to unregistered proxy', async () => {
// Construct metadata for ERC20 proxy
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2);
// Perform a transfer from makerAddress to takerAddress
const amount = new BigNumber(10);
return expectRevertOrAlwaysFailingTransactionAsync(
assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync(
encodedAssetData,
encodedAssetDataWithoutProxyId,
AssetProxyId.ERC20,
makerAddress,
takerAddress,
amount,

View File

@@ -781,20 +781,49 @@ describe('Exchange wrappers', () => {
expect(newBalances).to.be.deep.equal(erc20Balances);
});
it('should throw when a signedOrder does not use the same takerAssetAddress', async () => {
it('should not fill a signedOrder that does not use the same takerAssetAddress', async () => {
signedOrders = [
orderFactory.newSignedOrder(),
orderFactory.newSignedOrder(),
orderFactory.newSignedOrder({
takerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address),
}),
orderFactory.newSignedOrder(),
];
const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18);
const filledSignedOrders = signedOrders.slice(0, -1);
_.forEach(filledSignedOrders, signedOrder => {
erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][
defaultMakerAssetAddress
].minus(signedOrder.makerAssetAmount);
erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][
defaultTakerAssetAddress
].add(signedOrder.takerAssetAmount);
erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus(
signedOrder.makerFee,
);
erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][
defaultMakerAssetAddress
].add(signedOrder.makerAssetAmount);
erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][
defaultTakerAssetAddress
].minus(signedOrder.takerAssetAmount);
erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus(
signedOrder.takerFee,
);
erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][
zrxToken.address
].add(signedOrder.makerFee.add(signedOrder.takerFee));
});
await exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, {
takerAssetFillAmount,
// HACK(albrow): We need to hardcode the gas estimate here because
// the Geth gas estimator doesn't work with the way we use
// delegatecall and swallow errors.
gas: 600000,
});
return expectRevertOrAlwaysFailingTransactionAsync(
exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, {
takerAssetFillAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 18),
}),
);
const newBalances = await erc20Wrapper.getBalancesAsync();
expect(newBalances).to.be.deep.equal(erc20Balances);
});
});
@@ -894,6 +923,10 @@ describe('Exchange wrappers', () => {
);
await exchangeWrapper.marketBuyOrdersNoThrowAsync(signedOrders, takerAddress, {
makerAssetFillAmount,
// HACK(albrow): We need to hardcode the gas estimate here because
// the Geth gas estimator doesn't work with the way we use
// delegatecall and swallow errors.
gas: 600000,
});
const newBalances = await erc20Wrapper.getBalancesAsync();
@@ -926,8 +959,8 @@ describe('Exchange wrappers', () => {
);
});
it('should fill all signedOrders if cannot fill entire takerAssetFillAmount', async () => {
const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18);
it('should fill all signedOrders if cannot fill entire makerAssetFillAmount', async () => {
const makerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18);
_.forEach(signedOrders, signedOrder => {
erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][
defaultMakerAssetAddress
@@ -951,8 +984,8 @@ describe('Exchange wrappers', () => {
zrxToken.address
].add(signedOrder.makerFee.add(signedOrder.takerFee));
});
await exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, {
takerAssetFillAmount,
await exchangeWrapper.marketBuyOrdersNoThrowAsync(signedOrders, takerAddress, {
makerAssetFillAmount,
// HACK(albrow): We need to hardcode the gas estimate here because
// the Geth gas estimator doesn't work with the way we use
// delegatecall and swallow errors.
@@ -963,20 +996,50 @@ describe('Exchange wrappers', () => {
expect(newBalances).to.be.deep.equal(erc20Balances);
});
it('should throw when a signedOrder does not use the same makerAssetAddress', async () => {
it('should not fill a signedOrder that does not use the same makerAssetAddress', async () => {
signedOrders = [
orderFactory.newSignedOrder(),
orderFactory.newSignedOrder(),
orderFactory.newSignedOrder({
makerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address),
}),
orderFactory.newSignedOrder(),
];
return expectRevertOrAlwaysFailingTransactionAsync(
exchangeWrapper.marketBuyOrdersNoThrowAsync(signedOrders, takerAddress, {
makerAssetFillAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 18),
}),
);
const makerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18);
const filledSignedOrders = signedOrders.slice(0, -1);
_.forEach(filledSignedOrders, signedOrder => {
erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][
defaultMakerAssetAddress
].minus(signedOrder.makerAssetAmount);
erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][
defaultTakerAssetAddress
].add(signedOrder.takerAssetAmount);
erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus(
signedOrder.makerFee,
);
erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][
defaultMakerAssetAddress
].add(signedOrder.makerAssetAmount);
erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][
defaultTakerAssetAddress
].minus(signedOrder.takerAssetAmount);
erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus(
signedOrder.takerFee,
);
erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][
zrxToken.address
].add(signedOrder.makerFee.add(signedOrder.takerFee));
});
await exchangeWrapper.marketBuyOrdersNoThrowAsync(signedOrders, takerAddress, {
makerAssetFillAmount,
// HACK(albrow): We need to hardcode the gas estimate here because
// the Geth gas estimator doesn't work with the way we use
// delegatecall and swallow errors.
gas: 600000,
});
const newBalances = await erc20Wrapper.getBalancesAsync();
expect(newBalances).to.be.deep.equal(erc20Balances);
});
});