aboutsummaryrefslogtreecommitdiffstats
path: root/packages/order-utils/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/order-utils/src')
-rw-r--r--packages/order-utils/src/asset_data_utils.ts293
-rw-r--r--packages/order-utils/src/constants.ts66
-rw-r--r--packages/order-utils/src/exchange_transfer_simulator.ts64
-rw-r--r--packages/order-utils/src/index.ts5
-rw-r--r--packages/order-utils/src/order_state_utils.ts56
-rw-r--r--packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts2
6 files changed, 400 insertions, 86 deletions
diff --git a/packages/order-utils/src/asset_data_utils.ts b/packages/order-utils/src/asset_data_utils.ts
index 9bbef3a23..f314891e2 100644
--- a/packages/order-utils/src/asset_data_utils.ts
+++ b/packages/order-utils/src/asset_data_utils.ts
@@ -1,10 +1,19 @@
-import { AssetData, AssetProxyId, ERC20AssetData, ERC721AssetData } from '@0x/types';
-import { BigNumber } from '@0x/utils';
-import ethAbi = require('ethereumjs-abi');
-import ethUtil = require('ethereumjs-util');
+import {
+ AssetProxyId,
+ ERC20AssetData,
+ ERC721AssetData,
+ MultiAssetData,
+ MultiAssetDataWithRecursiveDecoding,
+ SingleAssetData,
+} from '@0x/types';
+import { AbiEncoder, BigNumber } from '@0x/utils';
+import * as _ from 'lodash';
import { constants } from './constants';
+const encodingRules: AbiEncoder.EncodingRules = { shouldOptimize: true };
+const decodingRules: AbiEncoder.DecodingRules = { shouldConvertStructsToObjects: true };
+
export const assetDataUtils = {
/**
* Encodes an ERC20 token address into a hex encoded assetData string, usable in the makerAssetData or
@@ -13,7 +22,10 @@ export const assetDataUtils = {
* @return The hex encoded assetData string
*/
encodeERC20AssetData(tokenAddress: string): string {
- return ethUtil.bufferToHex(ethAbi.simpleEncode('ERC20Token(address)', tokenAddress));
+ const abiEncoder = new AbiEncoder.Method(constants.ERC20_METHOD_ABI);
+ const args = [tokenAddress];
+ const assetData = abiEncoder.encode(args, encodingRules);
+ return assetData;
},
/**
* Decodes an ERC20 assetData hex string into it's corresponding ERC20 tokenAddress & assetProxyId
@@ -21,26 +33,14 @@ export const assetDataUtils = {
* @return An object containing the decoded tokenAddress & assetProxyId
*/
decodeERC20AssetData(assetData: string): ERC20AssetData {
- const data = ethUtil.toBuffer(assetData);
- if (data.byteLength < constants.ERC20_ASSET_DATA_BYTE_LENGTH) {
- throw new Error(
- `Could not decode ERC20 Proxy Data. Expected length of encoded data to be at least ${
- constants.ERC20_ASSET_DATA_BYTE_LENGTH
- }. Got ${data.byteLength}`,
- );
- }
- const assetProxyId = ethUtil.bufferToHex(data.slice(0, constants.SELECTOR_LENGTH));
- if (assetProxyId !== AssetProxyId.ERC20) {
- throw new Error(
- `Could not decode ERC20 Proxy Data. Expected Asset Proxy Id to be ERC20 (${
- AssetProxyId.ERC20
- }), but got ${assetProxyId}`,
- );
- }
- const [tokenAddress] = ethAbi.rawDecode(['address'], data.slice(constants.SELECTOR_LENGTH));
+ assetDataUtils.assertIsERC20AssetData(assetData);
+ const assetProxyId = assetDataUtils.decodeAssetProxyId(assetData);
+ const abiEncoder = new AbiEncoder.Method(constants.ERC20_METHOD_ABI);
+ const decodedAssetData = abiEncoder.decode(assetData, decodingRules);
return {
assetProxyId,
- tokenAddress: ethUtil.addHexPrefix(tokenAddress),
+ // TODO(abandeali1): fix return types for `AbiEncoder.Method.decode` so that we can remove type assertion
+ tokenAddress: (decodedAssetData as any).tokenContract,
};
},
/**
@@ -51,14 +51,10 @@ export const assetDataUtils = {
* @return The hex encoded assetData string
*/
encodeERC721AssetData(tokenAddress: string, tokenId: BigNumber): string {
- // TODO: Pass `tokendId` as a BigNumber.
- return ethUtil.bufferToHex(
- ethAbi.simpleEncode(
- 'ERC721Token(address,uint256)',
- tokenAddress,
- `0x${tokenId.toString(constants.BASE_16)}`,
- ),
- );
+ const abiEncoder = new AbiEncoder.Method(constants.ERC721_METHOD_ABI);
+ const args = [tokenAddress, tokenId];
+ const assetData = abiEncoder.encode(args, encodingRules);
+ return assetData;
},
/**
* Decodes an ERC721 assetData hex string into it's corresponding ERC721 tokenAddress, tokenId & assetProxyId
@@ -66,27 +62,99 @@ export const assetDataUtils = {
* @return An object containing the decoded tokenAddress, tokenId & assetProxyId
*/
decodeERC721AssetData(assetData: string): ERC721AssetData {
- const data = ethUtil.toBuffer(assetData);
- if (data.byteLength < constants.ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH) {
+ assetDataUtils.assertIsERC721AssetData(assetData);
+ const assetProxyId = assetDataUtils.decodeAssetProxyId(assetData);
+ const abiEncoder = new AbiEncoder.Method(constants.ERC721_METHOD_ABI);
+ const decodedAssetData = abiEncoder.decode(assetData, decodingRules);
+ return {
+ assetProxyId,
+ // TODO(abandeali1): fix return types for `AbiEncoder.Method.decode` so that we can remove type assertion
+ tokenAddress: (decodedAssetData as any).tokenContract,
+ tokenId: (decodedAssetData as any).tokenId,
+ };
+ },
+ /**
+ * Encodes assetData for multiple AssetProxies into a single hex encoded assetData string, usable in the makerAssetData or
+ * takerAssetData fields in a 0x order.
+ * @param amounts Amounts of each asset that correspond to a single unit within an order.
+ * @param nestedAssetData assetData strings that correspond to a valid assetProxyId.
+ * @return The hex encoded assetData string
+ */
+ encodeMultiAssetData(amounts: BigNumber[], nestedAssetData: string[]): string {
+ if (amounts.length !== nestedAssetData.length) {
throw new Error(
- `Could not decode ERC721 Asset Data. Expected length of encoded data to be at least ${
- constants.ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH
- }. Got ${data.byteLength}`,
+ `Invalid MultiAsset arguments. Expected length of 'amounts' (${
+ amounts.length
+ }) to equal length of 'nestedAssetData' (${nestedAssetData.length})`,
);
}
- const assetProxyId = ethUtil.bufferToHex(data.slice(0, constants.SELECTOR_LENGTH));
- if (assetProxyId !== AssetProxyId.ERC721) {
+ _.forEach(nestedAssetData, assetDataElement => assetDataUtils.validateAssetDataOrThrow(assetDataElement));
+ const abiEncoder = new AbiEncoder.Method(constants.MULTI_ASSET_METHOD_ABI);
+ const args = [amounts, nestedAssetData];
+ const assetData = abiEncoder.encode(args, encodingRules);
+ return assetData;
+ },
+ /**
+ * Decodes a MultiAsset assetData hex string into it's corresponding amounts and nestedAssetData
+ * @param assetData Hex encoded assetData string to decode
+ * @return An object containing the decoded amounts and nestedAssetData
+ */
+ decodeMultiAssetData(assetData: string): MultiAssetData {
+ assetDataUtils.assertIsMultiAssetData(assetData);
+ const assetProxyId = assetDataUtils.decodeAssetProxyId(assetData);
+ const abiEncoder = new AbiEncoder.Method(constants.MULTI_ASSET_METHOD_ABI);
+ const decodedAssetData = abiEncoder.decode(assetData, decodingRules);
+ // TODO(abandeali1): fix return types for `AbiEncoder.Method.decode` so that we can remove type assertion
+ const amounts = (decodedAssetData as any).amounts;
+ const nestedAssetData = (decodedAssetData as any).nestedAssetData;
+ if (amounts.length !== nestedAssetData.length) {
throw new Error(
- `Could not decode ERC721 Asset Data. Expected Asset Proxy Id to be ERC721 (${
- AssetProxyId.ERC721
- }), but got ${assetProxyId}`,
+ `Invalid MultiAsset assetData. Expected length of 'amounts' (${
+ amounts.length
+ }) to equal length of 'nestedAssetData' (${nestedAssetData.length})`,
);
}
- const [tokenAddress, tokenId] = ethAbi.rawDecode(['address', 'uint256'], data.slice(constants.SELECTOR_LENGTH));
return {
assetProxyId,
- tokenAddress: ethUtil.addHexPrefix(tokenAddress),
- tokenId: new BigNumber(tokenId.toString()),
+ amounts,
+ nestedAssetData,
+ };
+ },
+ /**
+ * Decodes a MultiAsset assetData hex string into it's corresponding amounts and decoded nestedAssetData elements (all nested elements are flattened)
+ * @param assetData Hex encoded assetData string to decode
+ * @return An object containing the decoded amounts and nestedAssetData
+ */
+ decodeMultiAssetDataRecursively(assetData: string): MultiAssetDataWithRecursiveDecoding {
+ const decodedAssetData = assetDataUtils.decodeMultiAssetData(assetData);
+ const amounts: any[] = [];
+ const decodedNestedAssetData = _.map(
+ decodedAssetData.nestedAssetData as string[],
+ (nestedAssetDataElement, index) => {
+ const decodedNestedAssetDataElement = assetDataUtils.decodeAssetDataOrThrow(nestedAssetDataElement);
+ if (decodedNestedAssetDataElement.assetProxyId === AssetProxyId.MultiAsset) {
+ const recursivelyDecodedAssetData = assetDataUtils.decodeMultiAssetDataRecursively(
+ nestedAssetDataElement,
+ );
+ amounts.push(
+ _.map(recursivelyDecodedAssetData.amounts, amountElement =>
+ amountElement.times(decodedAssetData.amounts[index]),
+ ),
+ );
+ return recursivelyDecodedAssetData.nestedAssetData;
+ } else {
+ amounts.push(decodedAssetData.amounts[index]);
+ return decodedNestedAssetDataElement as SingleAssetData;
+ }
+ },
+ );
+ const flattenedAmounts = _.flattenDeep(amounts);
+ const flattenedDecodedNestedAssetData = _.flattenDeep(decodedNestedAssetData);
+ return {
+ assetProxyId: decodedAssetData.assetProxyId,
+ amounts: flattenedAmounts,
+ // tslint:disable-next-line:no-unnecessary-type-assertion
+ nestedAssetData: flattenedDecodedNestedAssetData as SingleAssetData[],
};
},
/**
@@ -95,24 +163,133 @@ export const assetDataUtils = {
* @return The assetProxyId
*/
decodeAssetProxyId(assetData: string): AssetProxyId {
- const encodedAssetData = ethUtil.toBuffer(assetData);
- if (encodedAssetData.byteLength < constants.SELECTOR_LENGTH) {
+ if (assetData.length < constants.SELECTOR_CHAR_LENGTH_WITH_PREFIX) {
throw new Error(
- `Could not decode assetData. Expected length of encoded data to be at least 4. Got ${
- encodedAssetData.byteLength
+ `Could not decode assetData. Expected length of encoded data to be at least 10. Got ${
+ assetData.length
}`,
);
}
- const encodedAssetProxyId = encodedAssetData.slice(0, constants.SELECTOR_LENGTH);
- const assetProxyId = decodeAssetProxyId(encodedAssetProxyId);
+ const assetProxyId = assetData.slice(0, constants.SELECTOR_CHAR_LENGTH_WITH_PREFIX);
+ if (
+ assetProxyId !== AssetProxyId.ERC20 &&
+ assetProxyId !== AssetProxyId.ERC721 &&
+ assetProxyId !== AssetProxyId.MultiAsset
+ ) {
+ throw new Error(`Invalid assetProxyId: ${assetProxyId}`);
+ }
return assetProxyId;
},
/**
+ * Checks if the decoded asset data is valid ERC20 data
+ * @param decodedAssetData The decoded asset data to check
+ */
+ isERC20AssetData(decodedAssetData: SingleAssetData | MultiAssetData): decodedAssetData is ERC20AssetData {
+ return decodedAssetData.assetProxyId === AssetProxyId.ERC20;
+ },
+ /**
+ * Checks if the decoded asset data is valid ERC721 data
+ * @param decodedAssetData The decoded asset data to check
+ */
+ isERC721AssetData(decodedAssetData: SingleAssetData | MultiAssetData): decodedAssetData is ERC721AssetData {
+ return decodedAssetData.assetProxyId === AssetProxyId.ERC721;
+ },
+ /**
+ * Checks if the decoded asset data is valid MultiAsset data
+ * @param decodedAssetData The decoded asset data to check
+ */
+ isMultiAssetData(decodedAssetData: SingleAssetData | MultiAssetData): decodedAssetData is MultiAssetData {
+ return decodedAssetData.assetProxyId === AssetProxyId.MultiAsset;
+ },
+ /**
+ * Throws if the length or assetProxyId are invalid for the ERC20Proxy.
+ * @param assetData Hex encoded assetData string
+ */
+ assertIsERC20AssetData(assetData: string): void {
+ if (assetData.length < constants.ERC20_ASSET_DATA_MIN_CHAR_LENGTH_WITH_PREFIX) {
+ throw new Error(
+ `Could not decode ERC20 Proxy Data. Expected length of encoded data to be at least ${
+ constants.ERC20_ASSET_DATA_MIN_CHAR_LENGTH_WITH_PREFIX
+ }. Got ${assetData.length}`,
+ );
+ }
+ const assetProxyId = assetDataUtils.decodeAssetProxyId(assetData);
+ if (assetProxyId !== AssetProxyId.ERC20) {
+ throw new Error(
+ `Could not decode ERC20 assetData. Expected assetProxyId to be ERC20 (${
+ AssetProxyId.ERC20
+ }), but got ${assetProxyId}`,
+ );
+ }
+ },
+ /**
+ * Throws if the length or assetProxyId are invalid for the ERC721Proxy.
+ * @param assetData Hex encoded assetData string
+ */
+ assertIsERC721AssetData(assetData: string): void {
+ if (assetData.length < constants.ERC721_ASSET_DATA_MIN_CHAR_LENGTH_WITH_PREFIX) {
+ throw new Error(
+ `Could not decode ERC721 assetData. Expected length of encoded data to be at least ${
+ constants.ERC721_ASSET_DATA_MIN_CHAR_LENGTH_WITH_PREFIX
+ }. Got ${assetData.length}`,
+ );
+ }
+ const assetProxyId = assetDataUtils.decodeAssetProxyId(assetData);
+ if (assetProxyId !== AssetProxyId.ERC721) {
+ throw new Error(
+ `Could not decode ERC721 assetData. Expected assetProxyId to be ERC721 (${
+ AssetProxyId.ERC721
+ }), but got ${assetProxyId}`,
+ );
+ }
+ },
+ /**
+ * Throws if the length or assetProxyId are invalid for the MultiAssetProxy.
+ * @param assetData Hex encoded assetData string
+ */
+ assertIsMultiAssetData(assetData: string): void {
+ if (assetData.length < constants.MULTI_ASSET_DATA_MIN_CHAR_LENGTH_WITH_PREFIX) {
+ throw new Error(
+ `Could not decode MultiAsset assetData. Expected length of encoded data to be at least ${
+ constants.MULTI_ASSET_DATA_MIN_CHAR_LENGTH_WITH_PREFIX
+ }. Got ${assetData.length}`,
+ );
+ }
+ const assetProxyId = assetDataUtils.decodeAssetProxyId(assetData);
+ if (assetProxyId !== AssetProxyId.MultiAsset) {
+ throw new Error(
+ `Could not decode MultiAsset assetData. Expected assetProxyId to be MultiAsset (${
+ AssetProxyId.MultiAsset
+ }), but got ${assetProxyId}`,
+ );
+ }
+ },
+ /**
+ * Throws if the length or assetProxyId are invalid for the corresponding AssetProxy.
+ * @param assetData Hex encoded assetData string
+ */
+ validateAssetDataOrThrow(assetData: string): void {
+ const assetProxyId = assetDataUtils.decodeAssetProxyId(assetData);
+ switch (assetProxyId) {
+ case AssetProxyId.ERC20:
+ assetDataUtils.assertIsERC20AssetData(assetData);
+ break;
+ case AssetProxyId.ERC721:
+ assetDataUtils.assertIsERC721AssetData(assetData);
+ break;
+ case AssetProxyId.MultiAsset:
+ assetDataUtils.assertIsMultiAssetData(assetData);
+ break;
+ default:
+ throw new Error(`Unrecognized asset proxy id: ${assetProxyId}`);
+ }
+ },
+ /**
* Decode any assetData into it's corresponding assetData object
* @param assetData Hex encoded assetData string to decode
* @return Either a ERC20 or ERC721 assetData object
*/
- decodeAssetDataOrThrow(assetData: string): AssetData {
+ decodeAssetDataOrThrow(assetData: string): SingleAssetData | MultiAssetData {
const assetProxyId = assetDataUtils.decodeAssetProxyId(assetData);
switch (assetProxyId) {
case AssetProxyId.ERC20:
@@ -121,19 +298,11 @@ export const assetDataUtils = {
case AssetProxyId.ERC721:
const erc721AssetData = assetDataUtils.decodeERC721AssetData(assetData);
return erc721AssetData;
+ case AssetProxyId.MultiAsset:
+ const multiAssetData = assetDataUtils.decodeMultiAssetData(assetData);
+ return multiAssetData;
default:
throw new Error(`Unrecognized asset proxy id: ${assetProxyId}`);
}
},
};
-
-function decodeAssetProxyId(encodedAssetProxyId: Buffer): AssetProxyId {
- 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}`);
-}
diff --git a/packages/order-utils/src/constants.ts b/packages/order-utils/src/constants.ts
index 10029dcc3..a9a687719 100644
--- a/packages/order-utils/src/constants.ts
+++ b/packages/order-utils/src/constants.ts
@@ -1,16 +1,71 @@
import { BigNumber } from '@0x/utils';
+import { MethodAbi } from 'ethereum-types';
+
+const ERC20_METHOD_ABI: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'tokenContract',
+ type: 'address',
+ },
+ ],
+ name: 'ERC20Token',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+const ERC721_METHOD_ABI: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'tokenContract',
+ type: 'address',
+ },
+ {
+ name: 'tokenId',
+ type: 'uint256',
+ },
+ ],
+ name: 'ERC721Token',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+const MULTI_ASSET_METHOD_ABI: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'amounts',
+ type: 'uint256[]',
+ },
+ {
+ name: 'nestedAssetData',
+ type: 'bytes[]',
+ },
+ ],
+ name: 'MultiAsset',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
export const constants = {
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
NULL_BYTES: '0x',
+ NULL_ERC20_ASSET_DATA: '0xf47261b00000000000000000000000000000000000000000000000000000000000000000',
// tslint:disable-next-line:custom-no-magic-numbers
UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1),
TESTRPC_NETWORK_ID: 50,
ADDRESS_LENGTH: 20,
- ERC20_ASSET_DATA_BYTE_LENGTH: 36,
- ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH: 53,
- SELECTOR_LENGTH: 4,
- BASE_16: 16,
+ ERC20_ASSET_DATA_MIN_CHAR_LENGTH_WITH_PREFIX: 74,
+ ERC721_ASSET_DATA_MIN_CHAR_LENGTH_WITH_PREFIX: 136,
+ MULTI_ASSET_DATA_MIN_CHAR_LENGTH_WITH_PREFIX: 266,
+ SELECTOR_CHAR_LENGTH_WITH_PREFIX: 10,
INFINITE_TIMESTAMP_SEC: new BigNumber(2524604400), // Close to infinite
ZERO_AMOUNT: new BigNumber(0),
EIP712_DOMAIN_NAME: '0x Protocol',
@@ -48,4 +103,7 @@ export const constants = {
{ name: 'data', type: 'bytes' },
],
},
+ ERC20_METHOD_ABI,
+ ERC721_METHOD_ABI,
+ MULTI_ASSET_METHOD_ABI,
};
diff --git a/packages/order-utils/src/exchange_transfer_simulator.ts b/packages/order-utils/src/exchange_transfer_simulator.ts
index 7a38b35df..06621fd9e 100644
--- a/packages/order-utils/src/exchange_transfer_simulator.ts
+++ b/packages/order-utils/src/exchange_transfer_simulator.ts
@@ -1,7 +1,8 @@
-import { ExchangeContractErrs } from '@0x/types';
+import { AssetProxyId, ExchangeContractErrs } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { AbstractBalanceAndProxyAllowanceLazyStore } from './abstract/abstract_balance_and_proxy_allowance_lazy_store';
+import { assetDataUtils } from './asset_data_utils';
import { constants } from './constants';
import { TradeSide, TransferType } from './types';
@@ -74,24 +75,51 @@ export class ExchangeTransferSimulator {
tradeSide: TradeSide,
transferType: TransferType,
): Promise<void> {
- // HACK: When simulating an open order (e.g taker is NULL_ADDRESS), we don't want to adjust balances/
- // allowances for the taker. We do however, want to increase the balance of the maker since the maker
- // might be relying on those funds to fill subsequent orders or pay the order's fees.
- if (from === constants.NULL_ADDRESS && tradeSide === TradeSide.Taker) {
- await this._increaseBalanceAsync(assetData, to, amountInBaseUnits);
- return;
+ const assetProxyId = assetDataUtils.decodeAssetProxyId(assetData);
+ switch (assetProxyId) {
+ case AssetProxyId.ERC20:
+ case AssetProxyId.ERC721:
+ // HACK: When simulating an open order (e.g taker is NULL_ADDRESS), we don't want to adjust balances/
+ // allowances for the taker. We do however, want to increase the balance of the maker since the maker
+ // might be relying on those funds to fill subsequent orders or pay the order's fees.
+ if (from === constants.NULL_ADDRESS && tradeSide === TradeSide.Taker) {
+ await this._increaseBalanceAsync(assetData, to, amountInBaseUnits);
+ return;
+ }
+ const balance = await this._store.getBalanceAsync(assetData, from);
+ const proxyAllowance = await this._store.getProxyAllowanceAsync(assetData, from);
+ if (proxyAllowance.lessThan(amountInBaseUnits)) {
+ ExchangeTransferSimulator._throwValidationError(
+ FailureReason.ProxyAllowance,
+ tradeSide,
+ transferType,
+ );
+ }
+ if (balance.lessThan(amountInBaseUnits)) {
+ ExchangeTransferSimulator._throwValidationError(FailureReason.Balance, tradeSide, transferType);
+ }
+ await this._decreaseProxyAllowanceAsync(assetData, from, amountInBaseUnits);
+ await this._decreaseBalanceAsync(assetData, from, amountInBaseUnits);
+ await this._increaseBalanceAsync(assetData, to, amountInBaseUnits);
+ break;
+ case AssetProxyId.MultiAsset:
+ const decodedAssetData = assetDataUtils.decodeMultiAssetData(assetData);
+ for (const [index, nestedAssetDataElement] of decodedAssetData.nestedAssetData.entries()) {
+ const amountsElement = decodedAssetData.amounts[index];
+ const totalAmount = amountInBaseUnits.times(amountsElement);
+ await this.transferFromAsync(
+ nestedAssetDataElement,
+ from,
+ to,
+ totalAmount,
+ tradeSide,
+ transferType,
+ );
+ }
+ break;
+ default:
+ break;
}
- const balance = await this._store.getBalanceAsync(assetData, from);
- const proxyAllowance = await this._store.getProxyAllowanceAsync(assetData, from);
- if (proxyAllowance.lessThan(amountInBaseUnits)) {
- ExchangeTransferSimulator._throwValidationError(FailureReason.ProxyAllowance, tradeSide, transferType);
- }
- if (balance.lessThan(amountInBaseUnits)) {
- ExchangeTransferSimulator._throwValidationError(FailureReason.Balance, tradeSide, transferType);
- }
- await this._decreaseProxyAllowanceAsync(assetData, from, amountInBaseUnits);
- await this._decreaseBalanceAsync(assetData, from, amountInBaseUnits);
- await this._increaseBalanceAsync(assetData, to, amountInBaseUnits);
}
private async _decreaseProxyAllowanceAsync(
assetData: string,
diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts
index e70d43efb..2150a02e4 100644
--- a/packages/order-utils/src/index.ts
+++ b/packages/order-utils/src/index.ts
@@ -34,11 +34,14 @@ export {
OrderRelevantState,
OrderState,
ECSignature,
- AssetData,
+ SingleAssetData,
ERC20AssetData,
ERC721AssetData,
+ MultiAssetData,
+ MultiAssetDataWithRecursiveDecoding,
AssetProxyId,
SignatureType,
+ ObjectMap,
OrderStateValid,
OrderStateInvalid,
ExchangeContractErrs,
diff --git a/packages/order-utils/src/order_state_utils.ts b/packages/order-utils/src/order_state_utils.ts
index fe0d6c773..389419587 100644
--- a/packages/order-utils/src/order_state_utils.ts
+++ b/packages/order-utils/src/order_state_utils.ts
@@ -1,5 +1,6 @@
import {
ExchangeContractErrs,
+ ObjectMap,
OrderRelevantState,
OrderState,
OrderStateInvalid,
@@ -7,9 +8,11 @@ import {
SignedOrder,
} from '@0x/types';
import { BigNumber } from '@0x/utils';
+import * as _ from 'lodash';
import { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher';
import { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher';
+import { assetDataUtils } from './asset_data_utils';
import { orderHashUtils } from './order_hash';
import { OrderValidationUtils } from './order_validation_utils';
import { RemainingFillableCalculator } from './remaining_fillable_calculator';
@@ -18,7 +21,9 @@ import { utils } from './utils';
interface SidedOrderRelevantState {
isMakerSide: boolean;
traderBalance: BigNumber;
+ traderIndividualBalances: ObjectMap<BigNumber>;
traderProxyAllowance: BigNumber;
+ traderIndividualProxyAllowances: ObjectMap<BigNumber>;
traderFeeBalance: BigNumber;
traderFeeProxyAllowance: BigNumber;
filledTakerAssetAmount: BigNumber;
@@ -121,7 +126,9 @@ export class OrderStateUtils {
const sidedOrderRelevantState = {
isMakerSide: true,
traderBalance: orderRelevantState.makerBalance,
+ traderIndividualBalances: orderRelevantState.makerIndividualBalances,
traderProxyAllowance: orderRelevantState.makerProxyAllowance,
+ traderIndividualProxyAllowances: orderRelevantState.makerIndividualProxyAllowances,
traderFeeBalance: orderRelevantState.makerFeeBalance,
traderFeeProxyAllowance: orderRelevantState.makerFeeProxyAllowance,
filledTakerAssetAmount: orderRelevantState.filledTakerAssetAmount,
@@ -165,7 +172,9 @@ export class OrderStateUtils {
const orderRelevantState = {
makerBalance: sidedOrderRelevantState.traderBalance,
+ makerIndividualBalances: sidedOrderRelevantState.traderIndividualBalances,
makerProxyAllowance: sidedOrderRelevantState.traderProxyAllowance,
+ makerIndividualProxyAllowances: sidedOrderRelevantState.traderIndividualProxyAllowances,
makerFeeBalance: sidedOrderRelevantState.traderFeeBalance,
makerFeeProxyAllowance: sidedOrderRelevantState.traderFeeProxyAllowance,
filledTakerAssetAmount: sidedOrderRelevantState.filledTakerAssetAmount,
@@ -236,10 +245,12 @@ export class OrderStateUtils {
const isAssetZRX = assetData === zrxAssetData;
const traderBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(assetData, traderAddress);
+ const traderIndividualBalances = await this._getAssetBalancesAsync(assetData, traderAddress);
const traderProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
assetData,
traderAddress,
);
+ const traderIndividualProxyAllowances = await this._getAssetProxyAllowancesAsync(assetData, traderAddress);
const traderFeeBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(
zrxAssetData,
traderAddress,
@@ -278,7 +289,9 @@ export class OrderStateUtils {
const sidedOrderRelevantState = {
isMakerSide,
traderBalance,
+ traderIndividualBalances,
traderProxyAllowance,
+ traderIndividualProxyAllowances,
traderFeeBalance,
traderFeeProxyAllowance,
filledTakerAssetAmount,
@@ -287,4 +300,47 @@ export class OrderStateUtils {
};
return sidedOrderRelevantState;
}
+ private async _getAssetBalancesAsync(
+ assetData: string,
+ traderAddress: string,
+ initialBalances: ObjectMap<BigNumber> = {},
+ ): Promise<ObjectMap<BigNumber>> {
+ const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData);
+ let balances: ObjectMap<BigNumber> = { ...initialBalances };
+ if (assetDataUtils.isERC20AssetData(decodedAssetData) || assetDataUtils.isERC721AssetData(decodedAssetData)) {
+ const balance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(assetData, traderAddress);
+ const tokenAddress = decodedAssetData.tokenAddress;
+ balances[tokenAddress] = _.isUndefined(initialBalances[tokenAddress])
+ ? balance
+ : balances[tokenAddress].add(balance);
+ } else if (assetDataUtils.isMultiAssetData(decodedAssetData)) {
+ for (const assetDataElement of decodedAssetData.nestedAssetData) {
+ balances = await this._getAssetBalancesAsync(assetDataElement, traderAddress, balances);
+ }
+ }
+ return balances;
+ }
+ private async _getAssetProxyAllowancesAsync(
+ assetData: string,
+ traderAddress: string,
+ initialAllowances: ObjectMap<BigNumber> = {},
+ ): Promise<ObjectMap<BigNumber>> {
+ const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData);
+ let allowances: ObjectMap<BigNumber> = { ...initialAllowances };
+ if (assetDataUtils.isERC20AssetData(decodedAssetData) || assetDataUtils.isERC721AssetData(decodedAssetData)) {
+ const allowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
+ assetData,
+ traderAddress,
+ );
+ const tokenAddress = decodedAssetData.tokenAddress;
+ allowances[tokenAddress] = _.isUndefined(initialAllowances[tokenAddress])
+ ? allowance
+ : allowances[tokenAddress].add(allowance);
+ } else if (assetDataUtils.isMultiAssetData(decodedAssetData)) {
+ for (const assetDataElement of decodedAssetData.nestedAssetData) {
+ allowances = await this._getAssetBalancesAsync(assetDataElement, traderAddress, allowances);
+ }
+ }
+ return allowances;
+ }
}
diff --git a/packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts b/packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts
index f42a76d0c..ae3e36238 100644
--- a/packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts
+++ b/packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts
@@ -119,7 +119,7 @@ export class BalanceAndProxyAllowanceLazyStore implements AbstractBalanceAndProx
public deleteAllERC721ProxyAllowance(tokenAddress: string, userAddress: string): void {
for (const assetData in this._proxyAllowance) {
if (this._proxyAllowance.hasOwnProperty(assetData)) {
- const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData);
+ const decodedAssetData = assetDataUtils.decodeERC721AssetData(assetData);
if (
decodedAssetData.assetProxyId === AssetProxyId.ERC721 &&
decodedAssetData.tokenAddress === tokenAddress &&