Merge pull request #1363 from 0xProject/feat/order-utils/abiEncoder

Add MAP support to order-utils, order-watcher, and types
This commit is contained in:
Amir Bandeali 2019-01-07 11:30:00 -08:00 committed by GitHub
commit 3cdb85606a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 1110 additions and 400 deletions

View File

@ -715,7 +715,7 @@ describe('Asset Transfer Proxies', () => {
const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
const amounts = [erc20Amount]; const amounts = [erc20Amount];
const nestedAssetData = [erc20AssetData]; const nestedAssetData = [erc20AssetData];
const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
assetData, assetData,
fromAddress, fromAddress,
@ -778,7 +778,7 @@ describe('Asset Transfer Proxies', () => {
const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
const amounts = [erc20Amount1, erc20Amount2]; const amounts = [erc20Amount1, erc20Amount2];
const nestedAssetData = [erc20AssetData1, erc20AssetData2]; const nestedAssetData = [erc20AssetData1, erc20AssetData2];
const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
assetData, assetData,
fromAddress, fromAddress,
@ -811,7 +811,7 @@ describe('Asset Transfer Proxies', () => {
const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenB.address); const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenB.address);
const amounts = [erc20Amount1, erc20Amount2]; const amounts = [erc20Amount1, erc20Amount2];
const nestedAssetData = [erc20AssetData1, erc20AssetData2]; const nestedAssetData = [erc20AssetData1, erc20AssetData2];
const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
assetData, assetData,
fromAddress, fromAddress,
@ -849,7 +849,7 @@ describe('Asset Transfer Proxies', () => {
const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
const amounts = [erc721Amount]; const amounts = [erc721Amount];
const nestedAssetData = [erc721AssetData]; const nestedAssetData = [erc721AssetData];
const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
assetData, assetData,
fromAddress, fromAddress,
@ -881,7 +881,7 @@ describe('Asset Transfer Proxies', () => {
const erc721Amount = new BigNumber(1); const erc721Amount = new BigNumber(1);
const amounts = [erc721Amount, erc721Amount]; const amounts = [erc721Amount, erc721Amount];
const nestedAssetData = [erc721AssetData1, erc721AssetData2]; const nestedAssetData = [erc721AssetData1, erc721AssetData2];
const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
assetData, assetData,
fromAddress, fromAddress,
@ -913,7 +913,7 @@ describe('Asset Transfer Proxies', () => {
const erc721Amount = new BigNumber(1); const erc721Amount = new BigNumber(1);
const amounts = [erc721Amount, erc721Amount]; const amounts = [erc721Amount, erc721Amount];
const nestedAssetData = [erc721AssetData1, erc721AssetData2]; const nestedAssetData = [erc721AssetData1, erc721AssetData2];
const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
assetData, assetData,
fromAddress, fromAddress,
@ -946,7 +946,7 @@ describe('Asset Transfer Proxies', () => {
const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
const amounts = [erc20Amount, erc721Amount]; const amounts = [erc20Amount, erc721Amount];
const nestedAssetData = [erc20AssetData, erc721AssetData]; const nestedAssetData = [erc20AssetData, erc721AssetData];
const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
assetData, assetData,
fromAddress, fromAddress,
@ -984,10 +984,7 @@ describe('Asset Transfer Proxies', () => {
const amounts = [erc20Amount, erc721Amount]; const amounts = [erc20Amount, erc721Amount];
const nestedAssetData = [erc20AssetData, erc721AssetData]; const nestedAssetData = [erc20AssetData, erc721AssetData];
const extraData = '0102030405060708'; const extraData = '0102030405060708';
const assetData = `${assetDataInterface.MultiAsset.getABIEncodedTransactionData( const assetData = `${assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData)}${extraData}`;
amounts,
nestedAssetData,
)}${extraData}`;
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
assetData, assetData,
fromAddress, fromAddress,
@ -1024,7 +1021,7 @@ describe('Asset Transfer Proxies', () => {
const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenB.address); const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenB.address);
const amounts = [erc20Amount1, erc20Amount2]; const amounts = [erc20Amount1, erc20Amount2];
const nestedAssetData = [erc20AssetData1, erc20AssetData2]; const nestedAssetData = [erc20AssetData1, erc20AssetData2];
const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
assetData, assetData,
fromAddress, fromAddress,
@ -1085,7 +1082,7 @@ describe('Asset Transfer Proxies', () => {
erc721AssetData3, erc721AssetData3,
erc721AssetData4, erc721AssetData4,
]; ];
const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
assetData, assetData,
fromAddress, fromAddress,
@ -1143,7 +1140,7 @@ describe('Asset Transfer Proxies', () => {
const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
const amounts = [erc20Amount, erc721Amount]; const amounts = [erc20Amount, erc721Amount];
const nestedAssetData = [erc20AssetData, erc721AssetData]; const nestedAssetData = [erc20AssetData, erc721AssetData];
const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
assetData, assetData,
fromAddress, fromAddress,
@ -1169,6 +1166,7 @@ describe('Asset Transfer Proxies', () => {
const invalidErc721AssetData = `${invalidProxyId}${erc721AssetData.slice(10)}`; const invalidErc721AssetData = `${invalidProxyId}${erc721AssetData.slice(10)}`;
const amounts = [erc20Amount, erc721Amount]; const amounts = [erc20Amount, erc721Amount];
const nestedAssetData = [erc20AssetData, invalidErc721AssetData]; const nestedAssetData = [erc20AssetData, invalidErc721AssetData];
// HACK: This is used to get around validation built into assetDataUtils
const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
assetData, assetData,
@ -1192,6 +1190,7 @@ describe('Asset Transfer Proxies', () => {
const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
const amounts = [erc20Amount]; const amounts = [erc20Amount];
const nestedAssetData = [erc20AssetData, erc721AssetData]; const nestedAssetData = [erc20AssetData, erc721AssetData];
// HACK: This is used to get around validation built into assetDataUtils
const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
assetData, assetData,
@ -1214,7 +1213,7 @@ describe('Asset Transfer Proxies', () => {
const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
const amounts = [erc20Amount]; const amounts = [erc20Amount];
const nestedAssetData = [erc20AssetData]; const nestedAssetData = [erc20AssetData];
const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
assetData, assetData,
fromAddress, fromAddress,
@ -1238,6 +1237,7 @@ describe('Asset Transfer Proxies', () => {
const erc721AssetData = '0x123456'; const erc721AssetData = '0x123456';
const amounts = [erc20Amount, erc721Amount]; const amounts = [erc20Amount, erc721Amount];
const nestedAssetData = [erc20AssetData, erc721AssetData]; const nestedAssetData = [erc20AssetData, erc721AssetData];
// HACK: This is used to get around validation built into assetDataUtils
const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
assetData, assetData,
@ -1262,7 +1262,7 @@ describe('Asset Transfer Proxies', () => {
const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
const amounts = [erc20Amount, erc721Amount]; const amounts = [erc20Amount, erc721Amount];
const nestedAssetData = [erc20AssetData, erc721AssetData]; const nestedAssetData = [erc20AssetData, erc721AssetData];
const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData);
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
assetData, assetData,
fromAddress, fromAddress,

View File

@ -1,4 +1,3 @@
import { artifacts as interfacesArtifacts, IAssetDataContract } from '@0x/contracts-interfaces';
import { import {
chaiSetup, chaiSetup,
constants, constants,
@ -43,11 +42,6 @@ import { ExchangeWrapper } from '../utils/exchange_wrapper';
chaiSetup.configure(); chaiSetup.configure();
const expect = chai.expect; const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
const assetDataInterface = new IAssetDataContract(
interfacesArtifacts.IAssetData.compilerOutput.abi,
constants.NULL_ADDRESS,
provider,
);
// tslint:disable:no-unnecessary-type-assertion // tslint:disable:no-unnecessary-type-assertion
describe('Exchange core', () => { describe('Exchange core', () => {
let makerAddress: string; let makerAddress: string;
@ -777,10 +771,7 @@ describe('Exchange core', () => {
assetDataUtils.encodeERC20AssetData(erc20TokenA.address), assetDataUtils.encodeERC20AssetData(erc20TokenA.address),
assetDataUtils.encodeERC20AssetData(erc20TokenB.address), assetDataUtils.encodeERC20AssetData(erc20TokenB.address),
]; ];
const makerAssetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData( const makerAssetData = assetDataUtils.encodeMultiAssetData(makerAmounts, makerNestedAssetData);
makerAmounts,
makerNestedAssetData,
);
const makerAssetAmount = new BigNumber(1); const makerAssetAmount = new BigNumber(1);
const takerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); const takerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
const takerAssetAmount = new BigNumber(10); const takerAssetAmount = new BigNumber(10);
@ -830,10 +821,7 @@ describe('Exchange core', () => {
assetDataUtils.encodeERC20AssetData(erc20TokenA.address), assetDataUtils.encodeERC20AssetData(erc20TokenA.address),
assetDataUtils.encodeERC20AssetData(erc20TokenB.address), assetDataUtils.encodeERC20AssetData(erc20TokenB.address),
]; ];
const makerAssetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData( const makerAssetData = assetDataUtils.encodeMultiAssetData(makerAmounts, makerNestedAssetData);
makerAmounts,
makerNestedAssetData,
);
const makerAssetAmount = new BigNumber(1); const makerAssetAmount = new BigNumber(1);
const takerAmounts = [new BigNumber(10), new BigNumber(1)]; const takerAmounts = [new BigNumber(10), new BigNumber(1)];
const takerAssetId = erc721TakerAssetIds[0]; const takerAssetId = erc721TakerAssetIds[0];
@ -841,10 +829,7 @@ describe('Exchange core', () => {
assetDataUtils.encodeERC20AssetData(zrxToken.address), assetDataUtils.encodeERC20AssetData(zrxToken.address),
assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId),
]; ];
const takerAssetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData( const takerAssetData = assetDataUtils.encodeMultiAssetData(takerAmounts, takerNestedAssetData);
takerAmounts,
takerNestedAssetData,
);
const takerAssetAmount = new BigNumber(1); const takerAssetAmount = new BigNumber(1);
signedOrder = await orderFactory.newSignedOrderAsync({ signedOrder = await orderFactory.newSignedOrderAsync({
makerAssetData, makerAssetData,
@ -900,10 +885,7 @@ describe('Exchange core', () => {
assetDataUtils.encodeERC20AssetData(erc20TokenA.address), assetDataUtils.encodeERC20AssetData(erc20TokenA.address),
assetDataUtils.encodeERC20AssetData(erc20TokenB.address), assetDataUtils.encodeERC20AssetData(erc20TokenB.address),
]; ];
const makerAssetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData( const makerAssetData = assetDataUtils.encodeMultiAssetData(makerAmounts, makerNestedAssetData);
makerAmounts,
makerNestedAssetData,
);
const makerAssetAmount = new BigNumber(30); const makerAssetAmount = new BigNumber(30);
const takerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); const takerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
const takerAssetAmount = new BigNumber(10); const takerAssetAmount = new BigNumber(10);
@ -980,10 +962,7 @@ describe('Exchange core', () => {
assetDataUtils.encodeERC20AssetData(erc20TokenA.address), assetDataUtils.encodeERC20AssetData(erc20TokenA.address),
assetDataUtils.encodeERC20AssetData(erc20TokenB.address), assetDataUtils.encodeERC20AssetData(erc20TokenB.address),
]; ];
const takerAssetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData( const takerAssetData = assetDataUtils.encodeMultiAssetData(takerAmounts, takerNestedAssetData);
takerAmounts,
takerNestedAssetData,
);
const takerAssetAmount = new BigNumber(30); const takerAssetAmount = new BigNumber(30);
const makerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); const makerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
const makerAssetAmount = new BigNumber(10); const makerAssetAmount = new BigNumber(10);

View File

@ -22,6 +22,7 @@ import * as UnlimitedAllowanceToken_v1 from '../../generated-artifacts/Unlimited
import * as WETH9 from '../../generated-artifacts/WETH9.json'; import * as WETH9 from '../../generated-artifacts/WETH9.json';
import * as ZRXToken from '../../generated-artifacts/ZRXToken.json'; import * as ZRXToken from '../../generated-artifacts/ZRXToken.json';
// tslint:disable:no-unnecessary-type-assertion
export const artifacts = { export const artifacts = {
DummyERC20Token: DummyERC20Token as ContractArtifact, DummyERC20Token: DummyERC20Token as ContractArtifact,
DummyMultipleReturnERC20Token: DummyMultipleReturnERC20Token as ContractArtifact, DummyMultipleReturnERC20Token: DummyMultipleReturnERC20Token as ContractArtifact,

View File

@ -1,4 +1,14 @@
[ [
{
"version": "3.0.0",
"changes": [
{
"note":
"Export `MultiAssetData`, `MultiAssetDataWithRecursiveDecoding`, `ObjectMap`, and `SingleAssetData` from types. No longer export `AssetData`.",
"pr": 1363
}
]
},
{ {
"version": "2.0.8", "version": "2.0.8",
"changes": [ "changes": [

View File

@ -79,10 +79,13 @@ export {
OrderStateInvalid, OrderStateInvalid,
OrderState, OrderState,
AssetProxyId, AssetProxyId,
AssetData, SingleAssetData,
ERC20AssetData, ERC20AssetData,
ERC721AssetData, ERC721AssetData,
MultiAssetData,
MultiAssetDataWithRecursiveDecoding,
SignatureType, SignatureType,
ObjectMap,
OrderRelevantState, OrderRelevantState,
Stats, Stats,
} from '@0x/types'; } from '@0x/types';

View File

@ -1,9 +1,13 @@
[ [
{ {
"version": "4.1.4", "version": "4.2.0",
"changes": [ "changes": [
{ {
"note": "Add support for Trust Wallet signature denial error" "note": "Add support for Trust Wallet signature denial error"
},
{
"note": "Add balance and allowance queries for `MultiAssetProxy`",
"pr": 1363
} }
] ]
}, },

View File

@ -1,8 +1,7 @@
// tslint:disable:no-unnecessary-type-assertion
import { AbstractBalanceAndProxyAllowanceFetcher, assetDataUtils } from '@0x/order-utils'; import { AbstractBalanceAndProxyAllowanceFetcher, assetDataUtils } from '@0x/order-utils';
import { AssetProxyId, ERC20AssetData, ERC721AssetData } from '@0x/types';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import { BlockParamLiteral } from 'ethereum-types'; import { BlockParamLiteral } from 'ethereum-types';
import * as _ from 'lodash';
import { ERC20TokenWrapper } from '../contract_wrappers/erc20_token_wrapper'; import { ERC20TokenWrapper } from '../contract_wrappers/erc20_token_wrapper';
import { ERC721TokenWrapper } from '../contract_wrappers/erc721_token_wrapper'; import { ERC721TokenWrapper } from '../contract_wrappers/erc721_token_wrapper';
@ -18,42 +17,45 @@ export class AssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndP
} }
public async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber> { public async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber> {
const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData); const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData);
if (decodedAssetData.assetProxyId === AssetProxyId.ERC20) { let balance: BigNumber | undefined;
const decodedERC20AssetData = decodedAssetData as ERC20AssetData; if (assetDataUtils.isERC20AssetData(decodedAssetData)) {
const balance = await this._erc20Token.getBalanceAsync(decodedERC20AssetData.tokenAddress, userAddress, { balance = await this._erc20Token.getBalanceAsync(decodedAssetData.tokenAddress, userAddress, {
defaultBlock: this._stateLayer, defaultBlock: this._stateLayer,
}); });
return balance; } else if (assetDataUtils.isERC721AssetData(decodedAssetData)) {
} else {
const decodedERC721AssetData = decodedAssetData as ERC721AssetData;
const tokenOwner = await this._erc721Token.getOwnerOfAsync( const tokenOwner = await this._erc721Token.getOwnerOfAsync(
decodedERC721AssetData.tokenAddress, decodedAssetData.tokenAddress,
decodedERC721AssetData.tokenId, decodedAssetData.tokenId,
{ {
defaultBlock: this._stateLayer, defaultBlock: this._stateLayer,
}, },
); );
const balance = tokenOwner === userAddress ? new BigNumber(1) : new BigNumber(0); balance = tokenOwner === userAddress ? new BigNumber(1) : new BigNumber(0);
return balance; } else if (assetDataUtils.isMultiAssetData(decodedAssetData)) {
// The `balance` for MultiAssetData is the total units of the entire `assetData` that are held by the `userAddress`.
for (const [index, nestedAssetDataElement] of decodedAssetData.nestedAssetData.entries()) {
const nestedAmountElement = decodedAssetData.amounts[index];
const nestedAssetBalance = (await this.getBalanceAsync(
nestedAssetDataElement,
userAddress,
)).dividedToIntegerBy(nestedAmountElement);
if (_.isUndefined(balance) || nestedAssetBalance.lessThan(balance)) {
balance = nestedAssetBalance;
} }
} }
}
return balance as BigNumber;
}
public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber> { public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber> {
const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData); const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData);
if (decodedAssetData.assetProxyId === AssetProxyId.ERC20) { let proxyAllowance: BigNumber | undefined;
const decodedERC20AssetData = decodedAssetData as ERC20AssetData; if (assetDataUtils.isERC20AssetData(decodedAssetData)) {
const proxyAllowance = await this._erc20Token.getProxyAllowanceAsync( proxyAllowance = await this._erc20Token.getProxyAllowanceAsync(decodedAssetData.tokenAddress, userAddress, {
decodedERC20AssetData.tokenAddress,
userAddress,
{
defaultBlock: this._stateLayer, defaultBlock: this._stateLayer,
}, });
); } else if (assetDataUtils.isERC721AssetData(decodedAssetData)) {
return proxyAllowance;
} else {
const decodedERC721AssetData = decodedAssetData as ERC721AssetData;
const isApprovedForAll = await this._erc721Token.isProxyApprovedForAllAsync( const isApprovedForAll = await this._erc721Token.isProxyApprovedForAllAsync(
decodedERC721AssetData.tokenAddress, decodedAssetData.tokenAddress,
userAddress, userAddress,
{ {
defaultBlock: this._stateLayer, defaultBlock: this._stateLayer,
@ -63,15 +65,27 @@ export class AssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndP
return new BigNumber(this._erc20Token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); return new BigNumber(this._erc20Token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
} else { } else {
const isApproved = await this._erc721Token.isProxyApprovedAsync( const isApproved = await this._erc721Token.isProxyApprovedAsync(
decodedERC721AssetData.tokenAddress, decodedAssetData.tokenAddress,
decodedERC721AssetData.tokenId, decodedAssetData.tokenId,
{ {
defaultBlock: this._stateLayer, defaultBlock: this._stateLayer,
}, },
); );
const proxyAllowance = isApproved ? new BigNumber(1) : new BigNumber(0); proxyAllowance = isApproved ? new BigNumber(1) : new BigNumber(0);
return proxyAllowance; }
} else if (assetDataUtils.isMultiAssetData(decodedAssetData)) {
// The `proxyAllowance` for MultiAssetData is the total units of the entire `assetData` that the proxies have been approved to spend by the `userAddress`.
for (const [index, nestedAssetDataElement] of decodedAssetData.nestedAssetData.entries()) {
const nestedAmountElement = decodedAssetData.amounts[index];
const nestedAssetAllowance = (await this.getProxyAllowanceAsync(
nestedAssetDataElement,
userAddress,
)).dividedToIntegerBy(nestedAmountElement);
if (_.isUndefined(proxyAllowance) || nestedAssetAllowance.lessThan(proxyAllowance)) {
proxyAllowance = nestedAssetAllowance;
} }
} }
} }
return proxyAllowance as BigNumber;
}
} }

View File

@ -1,4 +1,13 @@
[ [
{
"version": "1.1.0",
"changes": [
{
"note": "Add support for MultiAssetProxy",
"pr": 1363
}
]
},
{ {
"version": "1.0.16", "version": "1.0.16",
"changes": [ "changes": [

View File

@ -2,7 +2,7 @@ import { DummyERC20TokenContract, DummyERC721TokenContract, ExchangeContract } f
import * as artifacts from '@0x/contract-artifacts'; import * as artifacts from '@0x/contract-artifacts';
import { assetDataUtils } from '@0x/order-utils'; import { assetDataUtils } from '@0x/order-utils';
import { orderFactory } from '@0x/order-utils/lib/src/order_factory'; import { orderFactory } from '@0x/order-utils/lib/src/order_factory';
import { AssetProxyId, ERC721AssetData, OrderWithoutExchangeAddress, SignedOrder } from '@0x/types'; import { OrderWithoutExchangeAddress, SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper'; import { Web3Wrapper } from '@0x/web3-wrapper';
import { Provider } from 'ethereum-types'; import { Provider } from 'ethereum-types';
@ -150,39 +150,8 @@ export class FillScenarios {
feeRecipientAddress: string, feeRecipientAddress: string,
expirationTimeSeconds?: BigNumber, expirationTimeSeconds?: BigNumber,
): Promise<SignedOrder> { ): Promise<SignedOrder> {
const decodedMakerAssetData = assetDataUtils.decodeAssetDataOrThrow(makerAssetData); await this._increaseBalanceAndAllowanceWithAssetDataAsync(makerAssetData, makerAddress, makerFillableAmount);
if (decodedMakerAssetData.assetProxyId === AssetProxyId.ERC20) { await this._increaseBalanceAndAllowanceWithAssetDataAsync(takerAssetData, takerAddress, takerFillableAmount);
await this._increaseERC20BalanceAndAllowanceAsync(
decodedMakerAssetData.tokenAddress,
makerAddress,
makerFillableAmount,
);
} else {
if (!makerFillableAmount.equals(1)) {
throw new Error(`ERC721 makerFillableAmount should be equal 1. Found: ${makerFillableAmount}`);
}
await this._increaseERC721BalanceAndAllowanceAsync(
decodedMakerAssetData.tokenAddress,
makerAddress,
// tslint:disable-next-line:no-unnecessary-type-assertion
(decodedMakerAssetData as ERC721AssetData).tokenId,
);
}
const decodedTakerAssetData = assetDataUtils.decodeAssetDataOrThrow(takerAssetData);
if (decodedTakerAssetData.assetProxyId === AssetProxyId.ERC20) {
const takerTokenAddress = decodedTakerAssetData.tokenAddress;
await this._increaseERC20BalanceAndAllowanceAsync(takerTokenAddress, takerAddress, takerFillableAmount);
} else {
if (!takerFillableAmount.equals(1)) {
throw new Error(`ERC721 takerFillableAmount should be equal 1. Found: ${takerFillableAmount}`);
}
await this._increaseERC721BalanceAndAllowanceAsync(
decodedTakerAssetData.tokenAddress,
takerAddress,
// tslint:disable-next-line:no-unnecessary-type-assertion
(decodedMakerAssetData as ERC721AssetData).tokenId,
);
}
// Fees // Fees
await Promise.all([ await Promise.all([
this._increaseERC20BalanceAndAllowanceAsync(this._zrxTokenAddress, makerAddress, makerFee), this._increaseERC20BalanceAndAllowanceAsync(this._zrxTokenAddress, makerAddress, makerFee),
@ -298,4 +267,30 @@ export class FillScenarios {
}); });
await this._web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS); await this._web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
} }
private async _increaseBalanceAndAllowanceWithAssetDataAsync(
assetData: string,
userAddress: string,
amount: BigNumber,
): Promise<void> {
const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData);
if (assetDataUtils.isERC20AssetData(decodedAssetData)) {
await this._increaseERC20BalanceAndAllowanceAsync(decodedAssetData.tokenAddress, userAddress, amount);
} else if (assetDataUtils.isERC721AssetData(decodedAssetData)) {
await this._increaseERC721BalanceAndAllowanceAsync(
decodedAssetData.tokenAddress,
userAddress,
decodedAssetData.tokenId,
);
} else if (assetDataUtils.isMultiAssetData(decodedAssetData)) {
for (const [index, nestedAssetDataElement] of decodedAssetData.nestedAssetData.entries()) {
const amountsElement = decodedAssetData.amounts[index];
const totalAmount = amount.times(amountsElement);
await this._increaseBalanceAndAllowanceWithAssetDataAsync(
nestedAssetDataElement,
userAddress,
totalAmount,
);
}
}
}
} }

View File

@ -1,4 +1,14 @@
[ [
{
"version": "3.1.0",
"changes": [
{
"note":
"Use new ABI encoder, add encoding/decoding logic for MultiAsset assetData, and add information to return values in orderStateUtils",
"pr": 1363
}
]
},
{ {
"version": "3.0.7", "version": "3.0.7",
"changes": [ "changes": [

View File

@ -1,10 +1,19 @@
import { AssetData, AssetProxyId, ERC20AssetData, ERC721AssetData } from '@0x/types'; import {
import { BigNumber } from '@0x/utils'; AssetProxyId,
import ethAbi = require('ethereumjs-abi'); ERC20AssetData,
import ethUtil = require('ethereumjs-util'); ERC721AssetData,
MultiAssetData,
MultiAssetDataWithRecursiveDecoding,
SingleAssetData,
} from '@0x/types';
import { AbiEncoder, BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import { constants } from './constants'; import { constants } from './constants';
const encodingRules: AbiEncoder.EncodingRules = { shouldOptimize: true };
const decodingRules: AbiEncoder.DecodingRules = { shouldConvertStructsToObjects: true };
export const assetDataUtils = { export const assetDataUtils = {
/** /**
* Encodes an ERC20 token address into a hex encoded assetData string, usable in the makerAssetData or * 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 * @return The hex encoded assetData string
*/ */
encodeERC20AssetData(tokenAddress: string): 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 * 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 * @return An object containing the decoded tokenAddress & assetProxyId
*/ */
decodeERC20AssetData(assetData: string): ERC20AssetData { decodeERC20AssetData(assetData: string): ERC20AssetData {
const data = ethUtil.toBuffer(assetData); assetDataUtils.assertIsERC20AssetData(assetData);
if (data.byteLength < constants.ERC20_ASSET_DATA_BYTE_LENGTH) { const assetProxyId = assetDataUtils.decodeAssetProxyId(assetData);
throw new Error( const abiEncoder = new AbiEncoder.Method(constants.ERC20_METHOD_ABI);
`Could not decode ERC20 Proxy Data. Expected length of encoded data to be at least ${ const decodedAssetData = abiEncoder.decode(assetData, decodingRules);
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));
return { return {
assetProxyId, 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 * @return The hex encoded assetData string
*/ */
encodeERC721AssetData(tokenAddress: string, tokenId: BigNumber): string { encodeERC721AssetData(tokenAddress: string, tokenId: BigNumber): string {
// TODO: Pass `tokendId` as a BigNumber. const abiEncoder = new AbiEncoder.Method(constants.ERC721_METHOD_ABI);
return ethUtil.bufferToHex( const args = [tokenAddress, tokenId];
ethAbi.simpleEncode( const assetData = abiEncoder.encode(args, encodingRules);
'ERC721Token(address,uint256)', return assetData;
tokenAddress,
`0x${tokenId.toString(constants.BASE_16)}`,
),
);
}, },
/** /**
* Decodes an ERC721 assetData hex string into it's corresponding ERC721 tokenAddress, tokenId & assetProxyId * 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 * @return An object containing the decoded tokenAddress, tokenId & assetProxyId
*/ */
decodeERC721AssetData(assetData: string): ERC721AssetData { decodeERC721AssetData(assetData: string): ERC721AssetData {
const data = ethUtil.toBuffer(assetData); assetDataUtils.assertIsERC721AssetData(assetData);
if (data.byteLength < constants.ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH) { const assetProxyId = assetDataUtils.decodeAssetProxyId(assetData);
throw new Error( const abiEncoder = new AbiEncoder.Method(constants.ERC721_METHOD_ABI);
`Could not decode ERC721 Asset Data. Expected length of encoded data to be at least ${ const decodedAssetData = abiEncoder.decode(assetData, decodingRules);
constants.ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH
}. Got ${data.byteLength}`,
);
}
const assetProxyId = ethUtil.bufferToHex(data.slice(0, constants.SELECTOR_LENGTH));
if (assetProxyId !== AssetProxyId.ERC721) {
throw new Error(
`Could not decode ERC721 Asset Data. Expected Asset Proxy Id to be ERC721 (${
AssetProxyId.ERC721
}), but got ${assetProxyId}`,
);
}
const [tokenAddress, tokenId] = ethAbi.rawDecode(['address', 'uint256'], data.slice(constants.SELECTOR_LENGTH));
return { return {
assetProxyId, assetProxyId,
tokenAddress: ethUtil.addHexPrefix(tokenAddress), // TODO(abandeali1): fix return types for `AbiEncoder.Method.decode` so that we can remove type assertion
tokenId: new BigNumber(tokenId.toString()), 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(
`Invalid MultiAsset arguments. Expected length of 'amounts' (${
amounts.length
}) to equal length of 'nestedAssetData' (${nestedAssetData.length})`,
);
}
_.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(
`Invalid MultiAsset assetData. Expected length of 'amounts' (${
amounts.length
}) to equal length of 'nestedAssetData' (${nestedAssetData.length})`,
);
}
return {
assetProxyId,
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 * @return The assetProxyId
*/ */
decodeAssetProxyId(assetData: string): AssetProxyId { decodeAssetProxyId(assetData: string): AssetProxyId {
const encodedAssetData = ethUtil.toBuffer(assetData); if (assetData.length < constants.SELECTOR_CHAR_LENGTH_WITH_PREFIX) {
if (encodedAssetData.byteLength < constants.SELECTOR_LENGTH) {
throw new Error( throw new Error(
`Could not decode assetData. Expected length of encoded data to be at least 4. Got ${ `Could not decode assetData. Expected length of encoded data to be at least 10. Got ${
encodedAssetData.byteLength assetData.length
}`, }`,
); );
} }
const encodedAssetProxyId = encodedAssetData.slice(0, constants.SELECTOR_LENGTH); const assetProxyId = assetData.slice(0, constants.SELECTOR_CHAR_LENGTH_WITH_PREFIX);
const assetProxyId = decodeAssetProxyId(encodedAssetProxyId); if (
assetProxyId !== AssetProxyId.ERC20 &&
assetProxyId !== AssetProxyId.ERC721 &&
assetProxyId !== AssetProxyId.MultiAsset
) {
throw new Error(`Invalid assetProxyId: ${assetProxyId}`);
}
return 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 * Decode any assetData into it's corresponding assetData object
* @param assetData Hex encoded assetData string to decode * @param assetData Hex encoded assetData string to decode
* @return Either a ERC20 or ERC721 assetData object * @return Either a ERC20 or ERC721 assetData object
*/ */
decodeAssetDataOrThrow(assetData: string): AssetData { decodeAssetDataOrThrow(assetData: string): SingleAssetData | MultiAssetData {
const assetProxyId = assetDataUtils.decodeAssetProxyId(assetData); const assetProxyId = assetDataUtils.decodeAssetProxyId(assetData);
switch (assetProxyId) { switch (assetProxyId) {
case AssetProxyId.ERC20: case AssetProxyId.ERC20:
@ -121,19 +298,11 @@ export const assetDataUtils = {
case AssetProxyId.ERC721: case AssetProxyId.ERC721:
const erc721AssetData = assetDataUtils.decodeERC721AssetData(assetData); const erc721AssetData = assetDataUtils.decodeERC721AssetData(assetData);
return erc721AssetData; return erc721AssetData;
case AssetProxyId.MultiAsset:
const multiAssetData = assetDataUtils.decodeMultiAssetData(assetData);
return multiAssetData;
default: default:
throw new Error(`Unrecognized asset proxy id: ${assetProxyId}`); 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}`);
}

View File

@ -1,16 +1,71 @@
import { BigNumber } from '@0x/utils'; 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 = { export const constants = {
NULL_ADDRESS: '0x0000000000000000000000000000000000000000', NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
NULL_BYTES: '0x', NULL_BYTES: '0x',
NULL_ERC20_ASSET_DATA: '0xf47261b00000000000000000000000000000000000000000000000000000000000000000',
// 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, ADDRESS_LENGTH: 20,
ERC20_ASSET_DATA_BYTE_LENGTH: 36, ERC20_ASSET_DATA_MIN_CHAR_LENGTH_WITH_PREFIX: 74,
ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH: 53, ERC721_ASSET_DATA_MIN_CHAR_LENGTH_WITH_PREFIX: 136,
SELECTOR_LENGTH: 4, MULTI_ASSET_DATA_MIN_CHAR_LENGTH_WITH_PREFIX: 266,
BASE_16: 16, SELECTOR_CHAR_LENGTH_WITH_PREFIX: 10,
INFINITE_TIMESTAMP_SEC: new BigNumber(2524604400), // Close to infinite INFINITE_TIMESTAMP_SEC: new BigNumber(2524604400), // Close to infinite
ZERO_AMOUNT: new BigNumber(0), ZERO_AMOUNT: new BigNumber(0),
EIP712_DOMAIN_NAME: '0x Protocol', EIP712_DOMAIN_NAME: '0x Protocol',
@ -48,4 +103,7 @@ export const constants = {
{ name: 'data', type: 'bytes' }, { name: 'data', type: 'bytes' },
], ],
}, },
ERC20_METHOD_ABI,
ERC721_METHOD_ABI,
MULTI_ASSET_METHOD_ABI,
}; };

View File

@ -1,7 +1,8 @@
import { ExchangeContractErrs } from '@0x/types'; import { AssetProxyId, ExchangeContractErrs } from '@0x/types';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import { AbstractBalanceAndProxyAllowanceLazyStore } from './abstract/abstract_balance_and_proxy_allowance_lazy_store'; import { AbstractBalanceAndProxyAllowanceLazyStore } from './abstract/abstract_balance_and_proxy_allowance_lazy_store';
import { assetDataUtils } from './asset_data_utils';
import { constants } from './constants'; import { constants } from './constants';
import { TradeSide, TransferType } from './types'; import { TradeSide, TransferType } from './types';
@ -74,6 +75,10 @@ export class ExchangeTransferSimulator {
tradeSide: TradeSide, tradeSide: TradeSide,
transferType: TransferType, transferType: TransferType,
): Promise<void> { ): Promise<void> {
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/ // 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 // 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. // might be relying on those funds to fill subsequent orders or pay the order's fees.
@ -84,7 +89,11 @@ export class ExchangeTransferSimulator {
const balance = await this._store.getBalanceAsync(assetData, from); const balance = await this._store.getBalanceAsync(assetData, from);
const proxyAllowance = await this._store.getProxyAllowanceAsync(assetData, from); const proxyAllowance = await this._store.getProxyAllowanceAsync(assetData, from);
if (proxyAllowance.lessThan(amountInBaseUnits)) { if (proxyAllowance.lessThan(amountInBaseUnits)) {
ExchangeTransferSimulator._throwValidationError(FailureReason.ProxyAllowance, tradeSide, transferType); ExchangeTransferSimulator._throwValidationError(
FailureReason.ProxyAllowance,
tradeSide,
transferType,
);
} }
if (balance.lessThan(amountInBaseUnits)) { if (balance.lessThan(amountInBaseUnits)) {
ExchangeTransferSimulator._throwValidationError(FailureReason.Balance, tradeSide, transferType); ExchangeTransferSimulator._throwValidationError(FailureReason.Balance, tradeSide, transferType);
@ -92,6 +101,25 @@ export class ExchangeTransferSimulator {
await this._decreaseProxyAllowanceAsync(assetData, from, amountInBaseUnits); await this._decreaseProxyAllowanceAsync(assetData, from, amountInBaseUnits);
await this._decreaseBalanceAsync(assetData, from, amountInBaseUnits); await this._decreaseBalanceAsync(assetData, from, amountInBaseUnits);
await this._increaseBalanceAsync(assetData, to, 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;
}
} }
private async _decreaseProxyAllowanceAsync( private async _decreaseProxyAllowanceAsync(
assetData: string, assetData: string,

View File

@ -34,11 +34,14 @@ export {
OrderRelevantState, OrderRelevantState,
OrderState, OrderState,
ECSignature, ECSignature,
AssetData, SingleAssetData,
ERC20AssetData, ERC20AssetData,
ERC721AssetData, ERC721AssetData,
MultiAssetData,
MultiAssetDataWithRecursiveDecoding,
AssetProxyId, AssetProxyId,
SignatureType, SignatureType,
ObjectMap,
OrderStateValid, OrderStateValid,
OrderStateInvalid, OrderStateInvalid,
ExchangeContractErrs, ExchangeContractErrs,

View File

@ -1,5 +1,6 @@
import { import {
ExchangeContractErrs, ExchangeContractErrs,
ObjectMap,
OrderRelevantState, OrderRelevantState,
OrderState, OrderState,
OrderStateInvalid, OrderStateInvalid,
@ -7,9 +8,11 @@ import {
SignedOrder, SignedOrder,
} from '@0x/types'; } from '@0x/types';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher'; import { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher';
import { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher'; import { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher';
import { assetDataUtils } from './asset_data_utils';
import { orderHashUtils } from './order_hash'; import { orderHashUtils } from './order_hash';
import { OrderValidationUtils } from './order_validation_utils'; import { OrderValidationUtils } from './order_validation_utils';
import { RemainingFillableCalculator } from './remaining_fillable_calculator'; import { RemainingFillableCalculator } from './remaining_fillable_calculator';
@ -18,7 +21,9 @@ import { utils } from './utils';
interface SidedOrderRelevantState { interface SidedOrderRelevantState {
isMakerSide: boolean; isMakerSide: boolean;
traderBalance: BigNumber; traderBalance: BigNumber;
traderIndividualBalances: ObjectMap<BigNumber>;
traderProxyAllowance: BigNumber; traderProxyAllowance: BigNumber;
traderIndividualProxyAllowances: ObjectMap<BigNumber>;
traderFeeBalance: BigNumber; traderFeeBalance: BigNumber;
traderFeeProxyAllowance: BigNumber; traderFeeProxyAllowance: BigNumber;
filledTakerAssetAmount: BigNumber; filledTakerAssetAmount: BigNumber;
@ -121,7 +126,9 @@ export class OrderStateUtils {
const sidedOrderRelevantState = { const sidedOrderRelevantState = {
isMakerSide: true, isMakerSide: true,
traderBalance: orderRelevantState.makerBalance, traderBalance: orderRelevantState.makerBalance,
traderIndividualBalances: orderRelevantState.makerIndividualBalances,
traderProxyAllowance: orderRelevantState.makerProxyAllowance, traderProxyAllowance: orderRelevantState.makerProxyAllowance,
traderIndividualProxyAllowances: orderRelevantState.makerIndividualProxyAllowances,
traderFeeBalance: orderRelevantState.makerFeeBalance, traderFeeBalance: orderRelevantState.makerFeeBalance,
traderFeeProxyAllowance: orderRelevantState.makerFeeProxyAllowance, traderFeeProxyAllowance: orderRelevantState.makerFeeProxyAllowance,
filledTakerAssetAmount: orderRelevantState.filledTakerAssetAmount, filledTakerAssetAmount: orderRelevantState.filledTakerAssetAmount,
@ -165,7 +172,9 @@ export class OrderStateUtils {
const orderRelevantState = { const orderRelevantState = {
makerBalance: sidedOrderRelevantState.traderBalance, makerBalance: sidedOrderRelevantState.traderBalance,
makerIndividualBalances: sidedOrderRelevantState.traderIndividualBalances,
makerProxyAllowance: sidedOrderRelevantState.traderProxyAllowance, makerProxyAllowance: sidedOrderRelevantState.traderProxyAllowance,
makerIndividualProxyAllowances: sidedOrderRelevantState.traderIndividualProxyAllowances,
makerFeeBalance: sidedOrderRelevantState.traderFeeBalance, makerFeeBalance: sidedOrderRelevantState.traderFeeBalance,
makerFeeProxyAllowance: sidedOrderRelevantState.traderFeeProxyAllowance, makerFeeProxyAllowance: sidedOrderRelevantState.traderFeeProxyAllowance,
filledTakerAssetAmount: sidedOrderRelevantState.filledTakerAssetAmount, filledTakerAssetAmount: sidedOrderRelevantState.filledTakerAssetAmount,
@ -236,10 +245,12 @@ export class OrderStateUtils {
const isAssetZRX = assetData === zrxAssetData; const isAssetZRX = assetData === zrxAssetData;
const traderBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(assetData, traderAddress); const traderBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(assetData, traderAddress);
const traderIndividualBalances = await this._getAssetBalancesAsync(assetData, traderAddress);
const traderProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync( const traderProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
assetData, assetData,
traderAddress, traderAddress,
); );
const traderIndividualProxyAllowances = await this._getAssetProxyAllowancesAsync(assetData, traderAddress);
const traderFeeBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync( const traderFeeBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(
zrxAssetData, zrxAssetData,
traderAddress, traderAddress,
@ -278,7 +289,9 @@ export class OrderStateUtils {
const sidedOrderRelevantState = { const sidedOrderRelevantState = {
isMakerSide, isMakerSide,
traderBalance, traderBalance,
traderIndividualBalances,
traderProxyAllowance, traderProxyAllowance,
traderIndividualProxyAllowances,
traderFeeBalance, traderFeeBalance,
traderFeeProxyAllowance, traderFeeProxyAllowance,
filledTakerAssetAmount, filledTakerAssetAmount,
@ -287,4 +300,47 @@ export class OrderStateUtils {
}; };
return sidedOrderRelevantState; 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;
}
} }

View File

@ -119,7 +119,7 @@ export class BalanceAndProxyAllowanceLazyStore implements AbstractBalanceAndProx
public deleteAllERC721ProxyAllowance(tokenAddress: string, userAddress: string): void { public deleteAllERC721ProxyAllowance(tokenAddress: string, userAddress: string): void {
for (const assetData in this._proxyAllowance) { for (const assetData in this._proxyAllowance) {
if (this._proxyAllowance.hasOwnProperty(assetData)) { if (this._proxyAllowance.hasOwnProperty(assetData)) {
const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData); const decodedAssetData = assetDataUtils.decodeERC721AssetData(assetData);
if ( if (
decodedAssetData.assetProxyId === AssetProxyId.ERC721 && decodedAssetData.assetProxyId === AssetProxyId.ERC721 &&
decodedAssetData.tokenAddress === tokenAddress && decodedAssetData.tokenAddress === tokenAddress &&

View File

@ -1,6 +1,6 @@
import * as chai from 'chai'; import * as chai from 'chai';
import { ERC20AssetData, ERC721AssetData } from '@0x/types'; import { AssetProxyId, ERC721AssetData } from '@0x/types';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import { assetDataUtils } from '../src/asset_data_utils'; import { assetDataUtils } from '../src/asset_data_utils';
@ -10,41 +10,101 @@ import { chaiSetup } from './utils/chai_setup';
chaiSetup.configure(); chaiSetup.configure();
const expect = chai.expect; const expect = chai.expect;
const KNOWN_ENCODINGS = [ const KNOWN_ERC20_ENCODING = {
{
address: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48', address: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48',
assetData: '0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48', assetData: '0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48',
}, };
{ const KNOWN_ERC721_ENCODING = {
address: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48', address: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48',
tokenId: new BigNumber(1), tokenId: new BigNumber(1),
assetData: assetData:
'0x025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001', '0x025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001',
}, };
]; const KNOWN_MULTI_ASSET_ENCODING = {
amounts: [new BigNumber(1), new BigNumber(1)],
const ERC20_ASSET_PROXY_ID = '0xf47261b0'; nestedAssetData: [KNOWN_ERC20_ENCODING.assetData, KNOWN_ERC721_ENCODING.assetData],
const ERC721_ASSET_PROXY_ID = '0x02571792'; assetData:
'0x94cfcdd7000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000024f47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000',
};
describe('assetDataUtils', () => { describe('assetDataUtils', () => {
it('should encode ERC20', () => { it('should encode ERC20', () => {
const assetData = assetDataUtils.encodeERC20AssetData(KNOWN_ENCODINGS[0].address); const assetData = assetDataUtils.encodeERC20AssetData(KNOWN_ERC20_ENCODING.address);
expect(assetData).to.equal(KNOWN_ENCODINGS[0].assetData); expect(assetData).to.equal(KNOWN_ERC20_ENCODING.assetData);
}); });
it('should decode ERC20', () => { it('should decode ERC20', () => {
const assetData: ERC20AssetData = assetDataUtils.decodeERC20AssetData(KNOWN_ENCODINGS[0].assetData); const decodedAssetData = assetDataUtils.decodeERC20AssetData(KNOWN_ERC20_ENCODING.assetData);
expect(assetData.tokenAddress).to.equal(KNOWN_ENCODINGS[0].address); expect(decodedAssetData.tokenAddress).to.equal(KNOWN_ERC20_ENCODING.address);
expect(assetData.assetProxyId).to.equal(ERC20_ASSET_PROXY_ID); expect(decodedAssetData.assetProxyId).to.equal(AssetProxyId.ERC20);
}); });
it('should encode ERC721', () => { it('should encode ERC721', () => {
const assetData = assetDataUtils.encodeERC721AssetData(KNOWN_ENCODINGS[1].address, KNOWN_ENCODINGS[1] const assetData = assetDataUtils.encodeERC721AssetData(
.tokenId as BigNumber); KNOWN_ERC721_ENCODING.address,
expect(assetData).to.equal(KNOWN_ENCODINGS[1].assetData); KNOWN_ERC721_ENCODING.tokenId,
);
expect(assetData).to.equal(KNOWN_ERC721_ENCODING.assetData);
}); });
it('should decode ERC721', () => { it('should decode ERC721', () => {
const assetData: ERC721AssetData = assetDataUtils.decodeERC721AssetData(KNOWN_ENCODINGS[1].assetData); const decodedAssetData = assetDataUtils.decodeERC721AssetData(KNOWN_ERC721_ENCODING.assetData);
expect(assetData.tokenAddress).to.equal(KNOWN_ENCODINGS[1].address); expect(decodedAssetData.tokenAddress).to.equal(KNOWN_ERC721_ENCODING.address);
expect(assetData.assetProxyId).to.equal(ERC721_ASSET_PROXY_ID); expect(decodedAssetData.assetProxyId).to.equal(AssetProxyId.ERC721);
expect(assetData.tokenId).to.be.bignumber.equal(KNOWN_ENCODINGS[1].tokenId); expect(decodedAssetData.tokenId).to.be.bignumber.equal(KNOWN_ERC721_ENCODING.tokenId);
});
it('should encode ERC20 and ERC721 multiAssetData', () => {
const assetData = assetDataUtils.encodeMultiAssetData(
KNOWN_MULTI_ASSET_ENCODING.amounts,
KNOWN_MULTI_ASSET_ENCODING.nestedAssetData,
);
expect(assetData).to.equal(KNOWN_MULTI_ASSET_ENCODING.assetData);
});
it('should decode ERC20 and ERC721 multiAssetData', () => {
const decodedAssetData = assetDataUtils.decodeMultiAssetData(KNOWN_MULTI_ASSET_ENCODING.assetData);
expect(decodedAssetData.assetProxyId).to.equal(AssetProxyId.MultiAsset);
expect(decodedAssetData.amounts).to.deep.equal(KNOWN_MULTI_ASSET_ENCODING.amounts);
expect(decodedAssetData.nestedAssetData).to.deep.equal(KNOWN_MULTI_ASSET_ENCODING.nestedAssetData);
});
it('should recursively decode ERC20 and ERC721 multiAssetData', () => {
const decodedAssetData = assetDataUtils.decodeMultiAssetDataRecursively(KNOWN_MULTI_ASSET_ENCODING.assetData);
expect(decodedAssetData.assetProxyId).to.equal(AssetProxyId.MultiAsset);
expect(decodedAssetData.amounts).to.deep.equal(KNOWN_MULTI_ASSET_ENCODING.amounts);
const decodedErc20AssetData = decodedAssetData.nestedAssetData[0];
// tslint:disable-next-line:no-unnecessary-type-assertion
const decodedErc721AssetData = decodedAssetData.nestedAssetData[1] as ERC721AssetData;
expect(decodedErc20AssetData.tokenAddress).to.equal(KNOWN_ERC20_ENCODING.address);
expect(decodedErc20AssetData.assetProxyId).to.equal(AssetProxyId.ERC20);
expect(decodedErc721AssetData.tokenAddress).to.equal(KNOWN_ERC721_ENCODING.address);
expect(decodedErc721AssetData.assetProxyId).to.equal(AssetProxyId.ERC721);
expect(decodedErc721AssetData.tokenId).to.be.bignumber.equal(KNOWN_ERC721_ENCODING.tokenId);
});
it('should recursively decode nested assetData within multiAssetData', () => {
const amounts = [new BigNumber(1), new BigNumber(1), new BigNumber(2)];
const nestedAssetData = [
KNOWN_ERC20_ENCODING.assetData,
KNOWN_ERC721_ENCODING.assetData,
KNOWN_MULTI_ASSET_ENCODING.assetData,
];
const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData);
const decodedAssetData = assetDataUtils.decodeMultiAssetDataRecursively(assetData);
expect(decodedAssetData.assetProxyId).to.equal(AssetProxyId.MultiAsset);
const expectedAmounts = [new BigNumber(1), new BigNumber(1), new BigNumber(2), new BigNumber(2)];
expect(decodedAssetData.amounts).to.deep.equal(expectedAmounts);
const expectedLength = 4;
expect(decodedAssetData.nestedAssetData.length).to.be.equal(expectedLength);
const decodedErc20AssetData1 = decodedAssetData.nestedAssetData[0];
// tslint:disable-next-line:no-unnecessary-type-assertion
const decodedErc721AssetData1 = decodedAssetData.nestedAssetData[1] as ERC721AssetData;
const decodedErc20AssetData2 = decodedAssetData.nestedAssetData[2];
// tslint:disable-next-line:no-unnecessary-type-assertion
const decodedErc721AssetData2 = decodedAssetData.nestedAssetData[3] as ERC721AssetData;
expect(decodedErc20AssetData1.tokenAddress).to.equal(KNOWN_ERC20_ENCODING.address);
expect(decodedErc20AssetData1.assetProxyId).to.equal(AssetProxyId.ERC20);
expect(decodedErc721AssetData1.tokenAddress).to.equal(KNOWN_ERC721_ENCODING.address);
expect(decodedErc721AssetData1.assetProxyId).to.equal(AssetProxyId.ERC721);
expect(decodedErc721AssetData1.tokenId).to.be.bignumber.equal(KNOWN_ERC721_ENCODING.tokenId);
expect(decodedErc20AssetData2.tokenAddress).to.equal(KNOWN_ERC20_ENCODING.address);
expect(decodedErc20AssetData2.assetProxyId).to.equal(AssetProxyId.ERC20);
expect(decodedErc721AssetData2.tokenAddress).to.equal(KNOWN_ERC721_ENCODING.address);
expect(decodedErc721AssetData2.assetProxyId).to.equal(AssetProxyId.ERC721);
expect(decodedErc721AssetData2.tokenId).to.be.bignumber.equal(KNOWN_ERC721_ENCODING.tokenId);
}); });
}); });

View File

@ -7,9 +7,9 @@ import { orderFactory } from '../../src/order_factory';
const BASE_TEST_ORDER: Order = orderFactory.createOrder( const BASE_TEST_ORDER: Order = orderFactory.createOrder(
constants.NULL_ADDRESS, constants.NULL_ADDRESS,
constants.ZERO_AMOUNT, constants.ZERO_AMOUNT,
constants.NULL_ADDRESS, constants.NULL_ERC20_ASSET_DATA,
constants.ZERO_AMOUNT, constants.ZERO_AMOUNT,
constants.NULL_ADDRESS, constants.NULL_ERC20_ASSET_DATA,
constants.NULL_ADDRESS, constants.NULL_ADDRESS,
); );
const BASE_TEST_SIGNED_ORDER: SignedOrder = { const BASE_TEST_SIGNED_ORDER: SignedOrder = {

View File

@ -1,4 +1,13 @@
[ [
{
"version": "2.4.0",
"changes": [
{
"note": "Add support for `MultiAssetProxy`",
"pr": 1363
}
]
},
{ {
"version": "2.3.0", "version": "2.3.0",
"changes": [ "changes": [

View File

@ -7,6 +7,7 @@ export {
OrderStateInvalid, OrderStateInvalid,
OrderState, OrderState,
ExchangeContractErrs, ExchangeContractErrs,
ObjectMap,
OrderRelevantState, OrderRelevantState,
Stats, Stats,
} from '@0x/types'; } from '@0x/types';

View File

@ -1,6 +1,5 @@
// tslint:disable:no-unnecessary-type-assertion
import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
import { AssetProxyId, ERC20AssetData, ERC721AssetData, SignedOrder } from '@0x/types'; import { AssetProxyId, SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
@ -62,35 +61,18 @@ export class DependentOrderHashesTracker {
return dependentOrderHashes; return dependentOrderHashes;
} }
public addToDependentOrderHashes(signedOrder: SignedOrder): void { public addToDependentOrderHashes(signedOrder: SignedOrder): void {
const decodedMakerAssetData = assetDataUtils.decodeAssetDataOrThrow(signedOrder.makerAssetData); this._addAssetDataToDependentOrderHashes(signedOrder, signedOrder.makerAssetData);
if (decodedMakerAssetData.assetProxyId === AssetProxyId.ERC20) {
this._addToERC20DependentOrderHashes(signedOrder, (decodedMakerAssetData as ERC20AssetData).tokenAddress);
} else {
this._addToERC721DependentOrderHashes(
signedOrder,
(decodedMakerAssetData as ERC721AssetData).tokenAddress,
(decodedMakerAssetData as ERC721AssetData).tokenId,
);
}
this._addToERC20DependentOrderHashes(signedOrder, this._zrxTokenAddress); this._addToERC20DependentOrderHashes(signedOrder, this._zrxTokenAddress);
this._addToMakerDependentOrderHashes(signedOrder); this._addToMakerDependentOrderHashes(signedOrder);
} }
public removeFromDependentOrderHashes(signedOrder: SignedOrder): void { public removeFromDependentOrderHashes(signedOrder: SignedOrder): void {
const decodedMakerAssetData = assetDataUtils.decodeAssetDataOrThrow(signedOrder.makerAssetData); this._removeAssetDataFromDependentOrderHashes(signedOrder, signedOrder.makerAssetData);
if (decodedMakerAssetData.assetProxyId === AssetProxyId.ERC20) {
this._removeFromERC20DependentOrderhashes(
signedOrder,
(decodedMakerAssetData as ERC20AssetData).tokenAddress,
);
} else {
this._removeFromERC721DependentOrderhashes(
signedOrder,
(decodedMakerAssetData as ERC721AssetData).tokenAddress,
(decodedMakerAssetData as ERC721AssetData).tokenId,
);
}
// If makerToken === ZRX then we already removed it and we don't need to remove it again. // If makerToken === ZRX then we already removed it and we don't need to remove it again.
if ((decodedMakerAssetData as ERC20AssetData).tokenAddress !== this._zrxTokenAddress) { const decodedMakerAssetData = assetDataUtils.decodeAssetDataOrThrow(signedOrder.makerAssetData);
if (
assetDataUtils.isERC20AssetData(decodedMakerAssetData) &&
decodedMakerAssetData.tokenAddress !== this._zrxTokenAddress
) {
this._removeFromERC20DependentOrderhashes(signedOrder, this._zrxTokenAddress); this._removeFromERC20DependentOrderhashes(signedOrder, this._zrxTokenAddress);
} }
this._removeFromMakerDependentOrderhashes(signedOrder); this._removeFromMakerDependentOrderhashes(signedOrder);
@ -167,6 +149,18 @@ export class DependentOrderHashesTracker {
tokenId.toString() tokenId.toString()
].add(orderHash); ].add(orderHash);
} }
private _addAssetDataToDependentOrderHashes(signedOrder: SignedOrder, assetData: string): void {
const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData);
if (assetDataUtils.isERC20AssetData(decodedAssetData)) {
this._addToERC20DependentOrderHashes(signedOrder, decodedAssetData.tokenAddress);
} else if (assetDataUtils.isERC721AssetData(decodedAssetData)) {
this._addToERC721DependentOrderHashes(signedOrder, decodedAssetData.tokenAddress, decodedAssetData.tokenId);
} else if (assetDataUtils.isMultiAssetData(decodedAssetData)) {
_.each(decodedAssetData.nestedAssetData, nestedAssetDataElement =>
this._addAssetDataToDependentOrderHashes(signedOrder, nestedAssetDataElement),
);
}
}
private _addToMakerDependentOrderHashes(signedOrder: SignedOrder): void { private _addToMakerDependentOrderHashes(signedOrder: SignedOrder): void {
const orderHash = orderHashUtils.getOrderHashHex(signedOrder); const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
if (_.isUndefined(this._orderHashesByMakerAddress[signedOrder.makerAddress])) { if (_.isUndefined(this._orderHashesByMakerAddress[signedOrder.makerAddress])) {
@ -230,4 +224,20 @@ export class DependentOrderHashesTracker {
delete this._orderHashesByMakerAddress[signedOrder.makerAddress]; delete this._orderHashesByMakerAddress[signedOrder.makerAddress];
} }
} }
private _removeAssetDataFromDependentOrderHashes(signedOrder: SignedOrder, assetData: string): void {
const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData);
if (assetDataUtils.isERC20AssetData(decodedAssetData)) {
this._removeFromERC20DependentOrderhashes(signedOrder, decodedAssetData.tokenAddress);
} else if (assetDataUtils.isERC721AssetData(decodedAssetData)) {
this._removeFromERC721DependentOrderhashes(
signedOrder,
decodedAssetData.tokenAddress,
decodedAssetData.tokenId,
);
} else if (assetDataUtils.isMultiAssetData(decodedAssetData)) {
_.each(decodedAssetData.nestedAssetData, nestedAssetDataElement =>
this._removeAssetDataFromDependentOrderHashes(signedOrder, nestedAssetDataElement),
);
}
}
} }

View File

@ -161,14 +161,7 @@ export class OrderWatcher {
this._dependentOrderHashesTracker.addToDependentOrderHashes(signedOrder); this._dependentOrderHashesTracker.addToDependentOrderHashes(signedOrder);
const orderAssetDatas = [signedOrder.makerAssetData, signedOrder.takerAssetData]; const orderAssetDatas = [signedOrder.makerAssetData, signedOrder.takerAssetData];
_.each(orderAssetDatas, assetData => { _.each(orderAssetDatas, assetData => this._addAssetDataToAbiDecoder(assetData));
const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData);
if (decodedAssetData.assetProxyId === AssetProxyId.ERC20) {
this._collisionResistantAbiDecoder.addERC20Token(decodedAssetData.tokenAddress);
} else if (decodedAssetData.assetProxyId === AssetProxyId.ERC721) {
this._collisionResistantAbiDecoder.addERC721Token(decodedAssetData.tokenAddress);
}
});
} }
/** /**
* Removes an order from the orderWatcher * Removes an order from the orderWatcher
@ -236,31 +229,71 @@ export class OrderWatcher {
await this._emitRevalidateOrdersAsync([orderHash]); await this._emitRevalidateOrdersAsync([orderHash]);
} }
} }
private _addAssetDataToAbiDecoder(assetData: string): void {
const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData);
if (assetDataUtils.isERC20AssetData(decodedAssetData)) {
this._collisionResistantAbiDecoder.addERC20Token(decodedAssetData.tokenAddress);
} else if (assetDataUtils.isERC721AssetData(decodedAssetData)) {
this._collisionResistantAbiDecoder.addERC721Token(decodedAssetData.tokenAddress);
} else if (assetDataUtils.isMultiAssetData(decodedAssetData)) {
_.each(decodedAssetData.nestedAssetData, nestedAssetDataElement =>
this._addAssetDataToAbiDecoder(nestedAssetDataElement),
);
}
}
private _deleteLazyStoreBalance(assetData: string, userAddress: string): void {
const assetProxyId = assetDataUtils.decodeAssetProxyId(assetData);
switch (assetProxyId) {
case AssetProxyId.ERC20:
case AssetProxyId.ERC721:
this._balanceAndProxyAllowanceLazyStore.deleteBalance(assetData, userAddress);
break;
case AssetProxyId.MultiAsset:
const decodedAssetData = assetDataUtils.decodeMultiAssetData(assetData);
_.each(decodedAssetData.nestedAssetData, nestedAssetDataElement =>
this._deleteLazyStoreBalance(nestedAssetDataElement, userAddress),
);
break;
default:
break;
}
}
private _deleteLazyStoreProxyAllowance(assetData: string, userAddress: string): void {
const assetProxyId = assetDataUtils.decodeAssetProxyId(assetData);
switch (assetProxyId) {
case AssetProxyId.ERC20:
case AssetProxyId.ERC721:
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(assetData, userAddress);
break;
case AssetProxyId.MultiAsset:
const decodedAssetData = assetDataUtils.decodeMultiAssetData(assetData);
_.each(decodedAssetData.nestedAssetData, nestedAssetDataElement =>
this._deleteLazyStoreProxyAllowance(nestedAssetDataElement, userAddress),
);
break;
default:
break;
}
}
private _cleanupOrderRelatedState(orderHash: string): void { private _cleanupOrderRelatedState(orderHash: string): void {
const signedOrder = this._orderByOrderHash[orderHash]; const signedOrder = this._orderByOrderHash[orderHash];
this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(orderHash); this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(orderHash);
this._orderFilledCancelledLazyStore.deleteIsCancelled(orderHash); this._orderFilledCancelledLazyStore.deleteIsCancelled(orderHash);
this._balanceAndProxyAllowanceLazyStore.deleteBalance(signedOrder.makerAssetData, signedOrder.makerAddress); this._deleteLazyStoreBalance(signedOrder.makerAssetData, signedOrder.makerAddress);
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance( this._deleteLazyStoreProxyAllowance(signedOrder.makerAssetData, signedOrder.makerAddress);
signedOrder.makerAssetData, this._deleteLazyStoreBalance(signedOrder.takerAssetData, signedOrder.takerAddress);
signedOrder.makerAddress, this._deleteLazyStoreProxyAllowance(signedOrder.takerAssetData, signedOrder.takerAddress);
);
this._balanceAndProxyAllowanceLazyStore.deleteBalance(signedOrder.takerAssetData, signedOrder.takerAddress);
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(
signedOrder.takerAssetData,
signedOrder.takerAddress,
);
const zrxAssetData = this._orderFilledCancelledLazyStore.getZRXAssetData(); const zrxAssetData = this._orderFilledCancelledLazyStore.getZRXAssetData();
if (!signedOrder.makerFee.isZero()) { if (!signedOrder.makerFee.isZero()) {
this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxAssetData, signedOrder.makerAddress); this._deleteLazyStoreBalance(zrxAssetData, signedOrder.makerAddress);
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxAssetData, signedOrder.makerAddress); this._deleteLazyStoreProxyAllowance(zrxAssetData, signedOrder.makerAddress);
} }
if (!signedOrder.takerFee.isZero()) { if (!signedOrder.takerFee.isZero()) {
this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxAssetData, signedOrder.takerAddress); this._deleteLazyStoreBalance(zrxAssetData, signedOrder.takerAddress);
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxAssetData, signedOrder.takerAddress); this._deleteLazyStoreProxyAllowance(zrxAssetData, signedOrder.takerAddress);
} }
} }
private _onOrderExpired(orderHash: string): void { private _onOrderExpired(orderHash: string): void {
@ -302,7 +335,7 @@ export class OrderWatcher {
// Invalidate cache // Invalidate cache
const args = decodedLog.args as ERC20TokenApprovalEventArgs; const args = decodedLog.args as ERC20TokenApprovalEventArgs;
const tokenAssetData = assetDataUtils.encodeERC20AssetData(decodedLog.address); const tokenAssetData = assetDataUtils.encodeERC20AssetData(decodedLog.address);
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(tokenAssetData, args._owner); this._deleteLazyStoreProxyAllowance(tokenAssetData, args._owner);
// Revalidate orders // Revalidate orders
const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker( const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker(
args._owner, args._owner,
@ -315,7 +348,7 @@ export class OrderWatcher {
// Invalidate cache // Invalidate cache
const args = decodedLog.args as ERC721TokenApprovalEventArgs; const args = decodedLog.args as ERC721TokenApprovalEventArgs;
const tokenAssetData = assetDataUtils.encodeERC721AssetData(decodedLog.address, args._tokenId); const tokenAssetData = assetDataUtils.encodeERC721AssetData(decodedLog.address, args._tokenId);
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(tokenAssetData, args._owner); this._deleteLazyStoreProxyAllowance(tokenAssetData, args._owner);
// Revalidate orders // Revalidate orders
const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker( const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker(
args._owner, args._owner,
@ -333,8 +366,8 @@ export class OrderWatcher {
// Invalidate cache // Invalidate cache
const args = decodedLog.args as ERC20TokenTransferEventArgs; const args = decodedLog.args as ERC20TokenTransferEventArgs;
const tokenAssetData = assetDataUtils.encodeERC20AssetData(decodedLog.address); const tokenAssetData = assetDataUtils.encodeERC20AssetData(decodedLog.address);
this._balanceAndProxyAllowanceLazyStore.deleteBalance(tokenAssetData, args._from); this._deleteLazyStoreBalance(tokenAssetData, args._from);
this._balanceAndProxyAllowanceLazyStore.deleteBalance(tokenAssetData, args._to); this._deleteLazyStoreBalance(tokenAssetData, args._to);
// Revalidate orders // Revalidate orders
const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker( const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker(
args._from, args._from,
@ -347,8 +380,8 @@ export class OrderWatcher {
// Invalidate cache // Invalidate cache
const args = decodedLog.args as ERC721TokenTransferEventArgs; const args = decodedLog.args as ERC721TokenTransferEventArgs;
const tokenAssetData = assetDataUtils.encodeERC721AssetData(decodedLog.address, args._tokenId); const tokenAssetData = assetDataUtils.encodeERC721AssetData(decodedLog.address, args._tokenId);
this._balanceAndProxyAllowanceLazyStore.deleteBalance(tokenAssetData, args._from); this._deleteLazyStoreBalance(tokenAssetData, args._from);
this._balanceAndProxyAllowanceLazyStore.deleteBalance(tokenAssetData, args._to); this._deleteLazyStoreBalance(tokenAssetData, args._to);
// Revalidate orders // Revalidate orders
const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker( const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker(
args._from, args._from,
@ -375,7 +408,7 @@ export class OrderWatcher {
// Invalidate cache // Invalidate cache
const args = decodedLog.args as WETH9DepositEventArgs; const args = decodedLog.args as WETH9DepositEventArgs;
const tokenAssetData = assetDataUtils.encodeERC20AssetData(decodedLog.address); const tokenAssetData = assetDataUtils.encodeERC20AssetData(decodedLog.address);
this._balanceAndProxyAllowanceLazyStore.deleteBalance(tokenAssetData, args._owner); this._deleteLazyStoreBalance(tokenAssetData, args._owner);
// Revalidate orders // Revalidate orders
const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker( const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker(
args._owner, args._owner,
@ -388,7 +421,7 @@ export class OrderWatcher {
// Invalidate cache // Invalidate cache
const args = decodedLog.args as WETH9WithdrawalEventArgs; const args = decodedLog.args as WETH9WithdrawalEventArgs;
const tokenAssetData = assetDataUtils.encodeERC20AssetData(decodedLog.address); const tokenAssetData = assetDataUtils.encodeERC20AssetData(decodedLog.address);
this._balanceAndProxyAllowanceLazyStore.deleteBalance(tokenAssetData, args._owner); this._deleteLazyStoreBalance(tokenAssetData, args._owner);
// Revalidate orders // Revalidate orders
const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker( const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker(
args._owner, args._owner,

View File

@ -675,5 +675,213 @@ describe('OrderWatcher', () => {
})().catch(done); })().catch(done);
}); });
}); });
describe('multiAsset', async () => {
const tokenId = new BigNumber(42);
const [makerErc721TokenAddress] = tokenUtils.getDummyERC721TokenAddresses();
const makerErc721AssetData = assetDataUtils.encodeERC721AssetData(makerErc721TokenAddress, tokenId);
const fillableErc721Amount = new BigNumber(1);
const [makerErc20TokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
const makerErc20AssetData = assetDataUtils.encodeERC20AssetData(makerErc20TokenAddress);
const fillableErc20Amount = new BigNumber(2);
const multiAssetAmounts = [fillableErc721Amount, fillableErc20Amount];
const nestedAssetData = [makerErc721AssetData, makerErc20AssetData];
const makerMultiAssetData = assetDataUtils.encodeMultiAssetData(multiAssetAmounts, nestedAssetData);
it('should emit orderStateInvalid when maker allowance of ERC721 token set to 0 for watched order', (done: DoneCallback) => {
(async () => {
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerMultiAssetData,
takerAssetData,
makerAddress,
takerAddress,
fillableErc721Amount,
);
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
await orderWatcher.addOrderAsync(signedOrder);
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.false();
const invalidOrderState = orderState as OrderStateInvalid;
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance);
});
orderWatcher.subscribe(callback);
await contractWrappers.erc721Token.setApprovalAsync(
makerErc721TokenAddress,
constants.NULL_ADDRESS,
tokenId,
);
})().catch(done);
});
it('should emit orderStateInvalid when maker allowance for all of ERC721 token set to 0 for watched order', (done: DoneCallback) => {
(async () => {
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerMultiAssetData,
takerAssetData,
makerAddress,
takerAddress,
fillableErc721Amount,
);
await contractWrappers.erc721Token.setApprovalAsync(
makerErc721TokenAddress,
constants.NULL_ADDRESS,
tokenId,
);
let isApproved = true;
await contractWrappers.erc721Token.setProxyApprovalForAllAsync(
makerErc721TokenAddress,
makerAddress,
isApproved,
);
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
await orderWatcher.addOrderAsync(signedOrder);
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.false();
const invalidOrderState = orderState as OrderStateInvalid;
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance);
});
orderWatcher.subscribe(callback);
isApproved = false;
await contractWrappers.erc721Token.setProxyApprovalForAllAsync(
makerErc721TokenAddress,
makerAddress,
isApproved,
);
})().catch(done);
});
it('should emit orderStateInvalid when maker moves ERC721 backing watched order', (done: DoneCallback) => {
(async () => {
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerMultiAssetData,
takerAssetData,
makerAddress,
takerAddress,
fillableErc721Amount,
);
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
await orderWatcher.addOrderAsync(signedOrder);
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.false();
const invalidOrderState = orderState as OrderStateInvalid;
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance);
});
orderWatcher.subscribe(callback);
await contractWrappers.erc721Token.transferFromAsync(
makerErc721TokenAddress,
coinbase,
makerAddress,
tokenId,
);
})().catch(done);
});
it('should emit orderStateInvalid when maker allowance of ERC20 token set to 0 for watched order', (done: DoneCallback) => {
(async () => {
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerMultiAssetData,
takerAssetData,
makerAddress,
takerAddress,
fillableErc721Amount,
);
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
await orderWatcher.addOrderAsync(signedOrder);
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.false();
const invalidOrderState = orderState as OrderStateInvalid;
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance);
});
orderWatcher.subscribe(callback);
await contractWrappers.erc20Token.setProxyAllowanceAsync(
makerErc20TokenAddress,
makerAddress,
new BigNumber(0),
);
})().catch(done);
});
it('should not emit an orderState event when irrelevant ERC20 Transfer event received', (done: DoneCallback) => {
(async () => {
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerMultiAssetData,
takerAssetData,
makerAddress,
takerAddress,
fillableAmount,
);
await orderWatcher.addOrderAsync(signedOrder);
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((_orderState: OrderState) => {
throw new Error('OrderState callback fired for irrelevant order');
});
orderWatcher.subscribe(callback);
const notTheMaker = userAddresses[0];
const anyRecipient = takerAddress;
const transferAmount = new BigNumber(2);
await contractWrappers.erc20Token.transferAsync(
makerTokenAddress,
notTheMaker,
anyRecipient,
transferAmount,
);
setTimeout(() => {
done();
}, TIMEOUT_MS);
})().catch(done);
});
it('should emit orderStateInvalid when makerAddress moves ERC20 balance backing watched order', (done: DoneCallback) => {
(async () => {
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerMultiAssetData,
takerAssetData,
makerAddress,
takerAddress,
fillableAmount,
);
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
await orderWatcher.addOrderAsync(signedOrder);
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.false();
const invalidOrderState = orderState as OrderStateInvalid;
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance);
});
orderWatcher.subscribe(callback);
const anyRecipient = takerAddress;
const makerBalance = await contractWrappers.erc20Token.getBalanceAsync(
makerTokenAddress,
makerAddress,
);
await contractWrappers.erc20Token.transferAsync(
makerTokenAddress,
makerAddress,
anyRecipient,
makerBalance,
);
})().catch(done);
});
// TODO(abandeali1): The following test will fail until the MAP has been deployed and activated.
it.skip('should emit orderStateInvalid when watched order fully filled', (done: DoneCallback) => {
(async () => {
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerMultiAssetData,
takerAssetData,
makerAddress,
takerAddress,
fillableAmount,
);
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
await orderWatcher.addOrderAsync(signedOrder);
const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.false();
const invalidOrderState = orderState as OrderStateInvalid;
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero);
});
orderWatcher.subscribe(callback);
await contractWrappers.exchange.fillOrderAsync(signedOrder, fillableAmount, takerAddress);
})().catch(done);
});
});
}); });
}); // tslint:disable:max-file-line-count }); // tslint:disable:max-file-line-count

View File

@ -26,7 +26,7 @@ interface WsMessage {
data: string; data: string;
} }
describe.only('OrderWatcherWebSocketServer', async () => { describe('OrderWatcherWebSocketServer', async () => {
let contractWrappers: ContractWrappers; let contractWrappers: ContractWrappers;
let wsServer: OrderWatcherWebSocketServer; let wsServer: OrderWatcherWebSocketServer;
let wsClient: WebSocket.w3cwebsocket; let wsClient: WebSocket.w3cwebsocket;

View File

@ -44,7 +44,7 @@
"@0x/contract-artifacts": "^1.0.1", "@0x/contract-artifacts": "^1.0.1",
"@0x/contract-wrappers": "^3.0.0", "@0x/contract-wrappers": "^3.0.0",
"@0x/dev-utils": "^1.0.21", "@0x/dev-utils": "^1.0.21",
"@0x/order-utils": "^2.0.0", "@0x/order-utils": "^3.0.7",
"@0x/subproviders": "^2.1.8", "@0x/subproviders": "^2.1.8",
"@0x/types": "^1.4.1", "@0x/types": "^1.4.1",
"@0x/utils": "^2.0.8", "@0x/utils": "^2.0.8",

View File

@ -1,7 +1,6 @@
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import { Column, Entity, PrimaryColumn } from 'typeorm'; import { Column, Entity, PrimaryColumn } from 'typeorm';
import { OrderType } from '../types';
import { bigNumberTransformer, numberToBigIntTransformer } from '../utils'; import { bigNumberTransformer, numberToBigIntTransformer } from '../utils';
@Entity({ name: 'token_orderbook_snapshots', schema: 'raw' }) @Entity({ name: 'token_orderbook_snapshots', schema: 'raw' })
@ -11,7 +10,7 @@ export class TokenOrderbookSnapshot {
@PrimaryColumn({ name: 'source' }) @PrimaryColumn({ name: 'source' })
public source!: string; public source!: string;
@PrimaryColumn({ name: 'order_type' }) @PrimaryColumn({ name: 'order_type' })
public orderType!: OrderType; public orderType!: string;
@PrimaryColumn({ name: 'price', type: 'numeric', transformer: bigNumberTransformer }) @PrimaryColumn({ name: 'price', type: 'numeric', transformer: bigNumberTransformer })
public price!: BigNumber; public price!: BigNumber;
@PrimaryColumn({ name: 'base_asset_symbol' }) @PrimaryColumn({ name: 'base_asset_symbol' })

View File

@ -23,8 +23,12 @@ export function parseDdexOrders(
): TokenOrder[] { ): TokenOrder[] {
const aggregatedBids = aggregateOrders(ddexOrderbook.bids); const aggregatedBids = aggregateOrders(ddexOrderbook.bids);
const aggregatedAsks = aggregateOrders(ddexOrderbook.asks); const aggregatedAsks = aggregateOrders(ddexOrderbook.asks);
const parsedBids = aggregatedBids.map(order => parseDdexOrder(ddexMarket, observedTimestamp, 'bid', source, order)); const parsedBids = aggregatedBids.map(order =>
const parsedAsks = aggregatedAsks.map(order => parseDdexOrder(ddexMarket, observedTimestamp, 'ask', source, order)); parseDdexOrder(ddexMarket, observedTimestamp, OrderType.Bid, source, order),
);
const parsedAsks = aggregatedAsks.map(order =>
parseDdexOrder(ddexMarket, observedTimestamp, OrderType.Ask, source, order),
);
return parsedBids.concat(parsedAsks); return parsedBids.concat(parsedAsks);
} }

View File

@ -5,7 +5,7 @@ import { LogWithDecodedArgs } from 'ethereum-types';
import * as R from 'ramda'; import * as R from 'ramda';
import { ExchangeCancelEvent, ExchangeCancelUpToEvent, ExchangeFillEvent } from '../../entities'; import { ExchangeCancelEvent, ExchangeCancelUpToEvent, ExchangeFillEvent } from '../../entities';
import { bigNumbertoStringOrNull } from '../../utils'; import { bigNumbertoStringOrNull, convertAssetProxyIdToType } from '../../utils';
/** /**
* Parses raw event logs for a fill event and returns an array of * Parses raw event logs for a fill event and returns an array of
@ -40,9 +40,7 @@ export const parseExchangeCancelUpToEvents: (
*/ */
export function _convertToExchangeFillEvent(eventLog: LogWithDecodedArgs<ExchangeFillEventArgs>): ExchangeFillEvent { export function _convertToExchangeFillEvent(eventLog: LogWithDecodedArgs<ExchangeFillEventArgs>): ExchangeFillEvent {
const makerAssetData = assetDataUtils.decodeAssetDataOrThrow(eventLog.args.makerAssetData); const makerAssetData = assetDataUtils.decodeAssetDataOrThrow(eventLog.args.makerAssetData);
const makerAssetType = makerAssetData.assetProxyId === AssetProxyId.ERC20 ? 'erc20' : 'erc721';
const takerAssetData = assetDataUtils.decodeAssetDataOrThrow(eventLog.args.takerAssetData); const takerAssetData = assetDataUtils.decodeAssetDataOrThrow(eventLog.args.takerAssetData);
const takerAssetType = takerAssetData.assetProxyId === AssetProxyId.ERC20 ? 'erc20' : 'erc721';
const exchangeFillEvent = new ExchangeFillEvent(); const exchangeFillEvent = new ExchangeFillEvent();
exchangeFillEvent.contractAddress = eventLog.address as string; exchangeFillEvent.contractAddress = eventLog.address as string;
exchangeFillEvent.blockNumber = eventLog.blockNumber as number; exchangeFillEvent.blockNumber = eventLog.blockNumber as number;
@ -59,16 +57,24 @@ export function _convertToExchangeFillEvent(eventLog: LogWithDecodedArgs<Exchang
exchangeFillEvent.takerFeePaid = eventLog.args.takerFeePaid; exchangeFillEvent.takerFeePaid = eventLog.args.takerFeePaid;
exchangeFillEvent.orderHash = eventLog.args.orderHash; exchangeFillEvent.orderHash = eventLog.args.orderHash;
exchangeFillEvent.rawMakerAssetData = eventLog.args.makerAssetData; exchangeFillEvent.rawMakerAssetData = eventLog.args.makerAssetData;
exchangeFillEvent.makerAssetType = makerAssetType; // tslint:disable-next-line:no-unnecessary-type-assertion
exchangeFillEvent.makerAssetType = convertAssetProxyIdToType(makerAssetData.assetProxyId as AssetProxyId);
exchangeFillEvent.makerAssetProxyId = makerAssetData.assetProxyId; exchangeFillEvent.makerAssetProxyId = makerAssetData.assetProxyId;
exchangeFillEvent.makerTokenAddress = makerAssetData.tokenAddress; // HACK(abandeali1): this event schema currently does not support multiple maker/taker assets, so we store the first token address from the MultiAssetProxy assetData
exchangeFillEvent.makerTokenAddress = assetDataUtils.isMultiAssetData(makerAssetData)
? assetDataUtils.decodeMultiAssetDataRecursively(eventLog.args.makerAssetData).nestedAssetData[0].tokenAddress
: makerAssetData.tokenAddress;
// tslint has a false positive here. Type assertion is required. // tslint has a false positive here. Type assertion is required.
// tslint:disable-next-line:no-unnecessary-type-assertion // tslint:disable-next-line:no-unnecessary-type-assertion
exchangeFillEvent.makerTokenId = bigNumbertoStringOrNull((makerAssetData as ERC721AssetData).tokenId); exchangeFillEvent.makerTokenId = bigNumbertoStringOrNull((makerAssetData as ERC721AssetData).tokenId);
exchangeFillEvent.rawTakerAssetData = eventLog.args.takerAssetData; exchangeFillEvent.rawTakerAssetData = eventLog.args.takerAssetData;
exchangeFillEvent.takerAssetType = takerAssetType; // tslint:disable-next-line:no-unnecessary-type-assertion
exchangeFillEvent.takerAssetType = convertAssetProxyIdToType(takerAssetData.assetProxyId as AssetProxyId);
exchangeFillEvent.takerAssetProxyId = takerAssetData.assetProxyId; exchangeFillEvent.takerAssetProxyId = takerAssetData.assetProxyId;
exchangeFillEvent.takerTokenAddress = takerAssetData.tokenAddress; // HACK(abandeali1): this event schema currently does not support multiple maker/taker assets, so we store the first token address from the MultiAssetProxy assetData
exchangeFillEvent.takerTokenAddress = assetDataUtils.isMultiAssetData(takerAssetData)
? assetDataUtils.decodeMultiAssetDataRecursively(eventLog.args.takerAssetData).nestedAssetData[0].tokenAddress
: takerAssetData.tokenAddress;
// tslint:disable-next-line:no-unnecessary-type-assertion // tslint:disable-next-line:no-unnecessary-type-assertion
exchangeFillEvent.takerTokenId = bigNumbertoStringOrNull((takerAssetData as ERC721AssetData).tokenId); exchangeFillEvent.takerTokenId = bigNumbertoStringOrNull((takerAssetData as ERC721AssetData).tokenId);
return exchangeFillEvent; return exchangeFillEvent;
@ -83,9 +89,7 @@ export function _convertToExchangeCancelEvent(
eventLog: LogWithDecodedArgs<ExchangeCancelEventArgs>, eventLog: LogWithDecodedArgs<ExchangeCancelEventArgs>,
): ExchangeCancelEvent { ): ExchangeCancelEvent {
const makerAssetData = assetDataUtils.decodeAssetDataOrThrow(eventLog.args.makerAssetData); const makerAssetData = assetDataUtils.decodeAssetDataOrThrow(eventLog.args.makerAssetData);
const makerAssetType = makerAssetData.assetProxyId === AssetProxyId.ERC20 ? 'erc20' : 'erc721';
const takerAssetData = assetDataUtils.decodeAssetDataOrThrow(eventLog.args.takerAssetData); const takerAssetData = assetDataUtils.decodeAssetDataOrThrow(eventLog.args.takerAssetData);
const takerAssetType = takerAssetData.assetProxyId === AssetProxyId.ERC20 ? 'erc20' : 'erc721';
const exchangeCancelEvent = new ExchangeCancelEvent(); const exchangeCancelEvent = new ExchangeCancelEvent();
exchangeCancelEvent.contractAddress = eventLog.address as string; exchangeCancelEvent.contractAddress = eventLog.address as string;
exchangeCancelEvent.blockNumber = eventLog.blockNumber as number; exchangeCancelEvent.blockNumber = eventLog.blockNumber as number;
@ -98,15 +102,23 @@ export function _convertToExchangeCancelEvent(
exchangeCancelEvent.senderAddress = eventLog.args.senderAddress; exchangeCancelEvent.senderAddress = eventLog.args.senderAddress;
exchangeCancelEvent.orderHash = eventLog.args.orderHash; exchangeCancelEvent.orderHash = eventLog.args.orderHash;
exchangeCancelEvent.rawMakerAssetData = eventLog.args.makerAssetData; exchangeCancelEvent.rawMakerAssetData = eventLog.args.makerAssetData;
exchangeCancelEvent.makerAssetType = makerAssetType; // tslint:disable-next-line:no-unnecessary-type-assertion
exchangeCancelEvent.makerAssetType = convertAssetProxyIdToType(makerAssetData.assetProxyId as AssetProxyId);
exchangeCancelEvent.makerAssetProxyId = makerAssetData.assetProxyId; exchangeCancelEvent.makerAssetProxyId = makerAssetData.assetProxyId;
exchangeCancelEvent.makerTokenAddress = makerAssetData.tokenAddress; // HACK(abandeali1): this event schema currently does not support multiple maker/taker assets, so we store the first token address from the MultiAssetProxy assetData
exchangeCancelEvent.makerTokenAddress = assetDataUtils.isMultiAssetData(makerAssetData)
? assetDataUtils.decodeMultiAssetDataRecursively(eventLog.args.makerAssetData).nestedAssetData[0].tokenAddress
: makerAssetData.tokenAddress;
// tslint:disable-next-line:no-unnecessary-type-assertion // tslint:disable-next-line:no-unnecessary-type-assertion
exchangeCancelEvent.makerTokenId = bigNumbertoStringOrNull((makerAssetData as ERC721AssetData).tokenId); exchangeCancelEvent.makerTokenId = bigNumbertoStringOrNull((makerAssetData as ERC721AssetData).tokenId);
exchangeCancelEvent.rawTakerAssetData = eventLog.args.takerAssetData; exchangeCancelEvent.rawTakerAssetData = eventLog.args.takerAssetData;
exchangeCancelEvent.takerAssetType = takerAssetType; // tslint:disable-next-line:no-unnecessary-type-assertion
exchangeCancelEvent.takerAssetType = convertAssetProxyIdToType(takerAssetData.assetProxyId as AssetProxyId);
exchangeCancelEvent.takerAssetProxyId = takerAssetData.assetProxyId; exchangeCancelEvent.takerAssetProxyId = takerAssetData.assetProxyId;
exchangeCancelEvent.takerTokenAddress = takerAssetData.tokenAddress; // HACK(abandeali1): this event schema currently does not support multiple maker/taker assets, so we store the first token address from the MultiAssetProxy assetData
exchangeCancelEvent.takerTokenAddress = assetDataUtils.isMultiAssetData(takerAssetData)
? assetDataUtils.decodeMultiAssetDataRecursively(eventLog.args.takerAssetData).nestedAssetData[0].tokenAddress
: takerAssetData.tokenAddress;
// tslint:disable-next-line:no-unnecessary-type-assertion // tslint:disable-next-line:no-unnecessary-type-assertion
exchangeCancelEvent.takerTokenId = bigNumbertoStringOrNull((takerAssetData as ERC721AssetData).tokenId); exchangeCancelEvent.takerTokenId = bigNumbertoStringOrNull((takerAssetData as ERC721AssetData).tokenId);
return exchangeCancelEvent; return exchangeCancelEvent;

View File

@ -2,7 +2,7 @@ import { BigNumber } from '@0x/utils';
import { aggregateOrders } from '../utils'; import { aggregateOrders } from '../utils';
import { IdexOrder, IdexOrderbook, IdexOrderParam } from '../../data_sources/idex'; import { IdexOrderbook, IdexOrderParam } from '../../data_sources/idex';
import { TokenOrderbookSnapshot as TokenOrder } from '../../entities'; import { TokenOrderbookSnapshot as TokenOrder } from '../../entities';
import { OrderType } from '../../types'; import { OrderType } from '../../types';
@ -21,7 +21,9 @@ export function parseIdexOrders(idexOrderbook: IdexOrderbook, observedTimestamp:
const idexBidOrder = idexOrderbook.bids[0]; const idexBidOrder = idexOrderbook.bids[0];
const parsedBids = const parsedBids =
aggregatedBids.length > 0 aggregatedBids.length > 0
? aggregatedBids.map(order => parseIdexOrder(idexBidOrder.params, observedTimestamp, 'bid', source, order)) ? aggregatedBids.map(order =>
parseIdexOrder(idexBidOrder.params, observedTimestamp, OrderType.Bid, source, order),
)
: []; : [];
const aggregatedAsks = aggregateOrders(idexOrderbook.asks); const aggregatedAsks = aggregateOrders(idexOrderbook.asks);
@ -29,7 +31,9 @@ export function parseIdexOrders(idexOrderbook: IdexOrderbook, observedTimestamp:
const idexAskOrder = idexOrderbook.asks[0]; const idexAskOrder = idexOrderbook.asks[0];
const parsedAsks = const parsedAsks =
aggregatedAsks.length > 0 aggregatedAsks.length > 0
? aggregatedAsks.map(order => parseIdexOrder(idexAskOrder.params, observedTimestamp, 'ask', source, order)) ? aggregatedAsks.map(order =>
parseIdexOrder(idexAskOrder.params, observedTimestamp, OrderType.Ask, source, order),
)
: []; : [];
return parsedBids.concat(parsedAsks); return parsedBids.concat(parsedAsks);
} }
@ -62,7 +66,7 @@ export function parseIdexOrder(
tokenOrder.baseVolume = amount; tokenOrder.baseVolume = amount;
tokenOrder.quoteVolume = price.times(amount); tokenOrder.quoteVolume = price.times(amount);
if (orderType === 'bid') { if (orderType === OrderType.Bid) {
tokenOrder.baseAssetSymbol = idexOrderParam.buySymbol; tokenOrder.baseAssetSymbol = idexOrderParam.buySymbol;
tokenOrder.baseAssetAddress = idexOrderParam.tokenBuy; tokenOrder.baseAssetAddress = idexOrderParam.tokenBuy;
tokenOrder.quoteAssetSymbol = idexOrderParam.sellSymbol; tokenOrder.quoteAssetSymbol = idexOrderParam.sellSymbol;

View File

@ -23,13 +23,13 @@ export function parseOasisOrders(
observedTimestamp: number, observedTimestamp: number,
source: string, source: string,
): TokenOrder[] { ): TokenOrder[] {
const aggregatedBids = aggregateOrders(R.filter(R.propEq('act', 'bid'), oasisOrderbook)); const aggregatedBids = aggregateOrders(R.filter(R.propEq('act', OrderType.Bid), oasisOrderbook));
const aggregatedAsks = aggregateOrders(R.filter(R.propEq('act', 'ask'), oasisOrderbook)); const aggregatedAsks = aggregateOrders(R.filter(R.propEq('act', OrderType.Ask), oasisOrderbook));
const parsedBids = aggregatedBids.map(order => const parsedBids = aggregatedBids.map(order =>
parseOasisOrder(oasisMarket, observedTimestamp, 'bid', source, order), parseOasisOrder(oasisMarket, observedTimestamp, OrderType.Bid, source, order),
); );
const parsedAsks = aggregatedAsks.map(order => const parsedAsks = aggregatedAsks.map(order =>
parseOasisOrder(oasisMarket, observedTimestamp, 'ask', source, order), parseOasisOrder(oasisMarket, observedTimestamp, OrderType.Ask, source, order),
); );
return parsedBids.concat(parsedAsks); return parsedBids.concat(parsedAsks);
} }

View File

@ -21,10 +21,10 @@ export function parseParadexOrders(
source: string, source: string,
): TokenOrder[] { ): TokenOrder[] {
const parsedBids = paradexOrderbookResponse.bids.map(order => const parsedBids = paradexOrderbookResponse.bids.map(order =>
parseParadexOrder(paradexMarket, observedTimestamp, 'bid', source, order), parseParadexOrder(paradexMarket, observedTimestamp, OrderType.Bid, source, order),
); );
const parsedAsks = paradexOrderbookResponse.asks.map(order => const parsedAsks = paradexOrderbookResponse.asks.map(order =>
parseParadexOrder(paradexMarket, observedTimestamp, 'ask', source, order), parseParadexOrder(paradexMarket, observedTimestamp, OrderType.Ask, source, order),
); );
return parsedBids.concat(parsedAsks); return parsedBids.concat(parsedAsks);
} }

View File

@ -4,7 +4,7 @@ import { AssetProxyId, ERC721AssetData } from '@0x/types';
import * as R from 'ramda'; import * as R from 'ramda';
import { SraOrder } from '../../entities'; import { SraOrder } from '../../entities';
import { bigNumbertoStringOrNull } from '../../utils'; import { bigNumbertoStringOrNull, convertAssetProxyIdToType } from '../../utils';
/** /**
* Parses a raw order response from an SRA endpoint and returns an array of * Parses a raw order response from an SRA endpoint and returns an array of
@ -22,9 +22,7 @@ export function parseSraOrders(rawOrdersResponse: OrdersResponse): SraOrder[] {
export function _convertToEntity(apiOrder: APIOrder): SraOrder { export function _convertToEntity(apiOrder: APIOrder): SraOrder {
// TODO(albrow): refactor out common asset data decoding code. // TODO(albrow): refactor out common asset data decoding code.
const makerAssetData = assetDataUtils.decodeAssetDataOrThrow(apiOrder.order.makerAssetData); const makerAssetData = assetDataUtils.decodeAssetDataOrThrow(apiOrder.order.makerAssetData);
const makerAssetType = makerAssetData.assetProxyId === AssetProxyId.ERC20 ? 'erc20' : 'erc721';
const takerAssetData = assetDataUtils.decodeAssetDataOrThrow(apiOrder.order.takerAssetData); const takerAssetData = assetDataUtils.decodeAssetDataOrThrow(apiOrder.order.takerAssetData);
const takerAssetType = takerAssetData.assetProxyId === AssetProxyId.ERC20 ? 'erc20' : 'erc721';
const sraOrder = new SraOrder(); const sraOrder = new SraOrder();
sraOrder.exchangeAddress = apiOrder.order.exchangeAddress; sraOrder.exchangeAddress = apiOrder.order.exchangeAddress;
@ -43,16 +41,24 @@ export function _convertToEntity(apiOrder: APIOrder): SraOrder {
sraOrder.signature = apiOrder.order.signature; sraOrder.signature = apiOrder.order.signature;
sraOrder.rawMakerAssetData = apiOrder.order.makerAssetData; sraOrder.rawMakerAssetData = apiOrder.order.makerAssetData;
sraOrder.makerAssetType = makerAssetType; // tslint:disable-next-line:no-unnecessary-type-assertion
sraOrder.makerAssetType = convertAssetProxyIdToType(makerAssetData.assetProxyId as AssetProxyId);
sraOrder.makerAssetProxyId = makerAssetData.assetProxyId; sraOrder.makerAssetProxyId = makerAssetData.assetProxyId;
sraOrder.makerTokenAddress = makerAssetData.tokenAddress; // HACK(abandeali1): this event schema currently does not support multiple maker/taker assets, so we store the first token address from the MultiAssetProxy assetData
sraOrder.makerTokenAddress = assetDataUtils.isMultiAssetData(makerAssetData)
? assetDataUtils.decodeMultiAssetDataRecursively(apiOrder.order.makerAssetData).nestedAssetData[0].tokenAddress
: makerAssetData.tokenAddress;
// tslint has a false positive here. Type assertion is required. // tslint has a false positive here. Type assertion is required.
// tslint:disable-next-line:no-unnecessary-type-assertion // tslint:disable-next-line:no-unnecessary-type-assertion
sraOrder.makerTokenId = bigNumbertoStringOrNull((makerAssetData as ERC721AssetData).tokenId); sraOrder.makerTokenId = bigNumbertoStringOrNull((makerAssetData as ERC721AssetData).tokenId);
sraOrder.rawTakerAssetData = apiOrder.order.takerAssetData; sraOrder.rawTakerAssetData = apiOrder.order.takerAssetData;
sraOrder.takerAssetType = takerAssetType; // tslint:disable-next-line:no-unnecessary-type-assertion
sraOrder.takerAssetType = convertAssetProxyIdToType(takerAssetData.assetProxyId as AssetProxyId);
sraOrder.takerAssetProxyId = takerAssetData.assetProxyId; sraOrder.takerAssetProxyId = takerAssetData.assetProxyId;
sraOrder.takerTokenAddress = takerAssetData.tokenAddress; // HACK(abandeali1): this event schema currently does not support multiple maker/taker assets, so we store the first token address from the MultiAssetProxy assetData
sraOrder.takerTokenAddress = assetDataUtils.isMultiAssetData(takerAssetData)
? assetDataUtils.decodeMultiAssetDataRecursively(apiOrder.order.takerAssetData).nestedAssetData[0].tokenAddress
: takerAssetData.tokenAddress;
// tslint:disable-next-line:no-unnecessary-type-assertion // tslint:disable-next-line:no-unnecessary-type-assertion
sraOrder.takerTokenId = bigNumbertoStringOrNull((takerAssetData as ERC721AssetData).tokenId); sraOrder.takerTokenId = bigNumbertoStringOrNull((takerAssetData as ERC721AssetData).tokenId);

View File

@ -1,2 +1,9 @@
export type AssetType = 'erc20' | 'erc721'; export enum AssetType {
export type OrderType = 'bid' | 'ask'; ERC20 = 'erc20',
ERC721 = 'erc721',
MultiAsset = 'multiAsset',
}
export enum OrderType {
Bid = 'bid',
Ask = 'ask',
}

View File

@ -0,0 +1,20 @@
import { AssetProxyId } from '@0x/types';
import { AssetType } from '../../types';
/**
* Converts an assetProxyId to its string equivalent
* @param assetProxyId Id of AssetProxy
*/
export function convertAssetProxyIdToType(assetProxyId: AssetProxyId): AssetType {
switch (assetProxyId) {
case AssetProxyId.ERC20:
return AssetType.ERC20;
case AssetProxyId.ERC721:
return AssetType.ERC721;
case AssetProxyId.MultiAsset:
return AssetType.MultiAsset;
default:
throw new Error(`${assetProxyId} not a supported assetProxyId`);
}
}

View File

@ -1,2 +1,3 @@
export * from './big_number'; export * from './big_number';
export * from './number_to_bigint'; export * from './number_to_bigint';
export * from './asset_proxy_id_types';

View File

@ -31,13 +31,13 @@ describe('ddex_orders', () => {
amountDecimals: 0, amountDecimals: 0,
}; };
const observedTimestamp: number = Date.now(); const observedTimestamp: number = Date.now();
const orderType: OrderType = 'bid'; const orderType: OrderType = OrderType.Bid;
const source: string = 'ddex'; const source: string = 'ddex';
const expected = new TokenOrder(); const expected = new TokenOrder();
expected.source = 'ddex'; expected.source = 'ddex';
expected.observedTimestamp = observedTimestamp; expected.observedTimestamp = observedTimestamp;
expected.orderType = 'bid'; expected.orderType = OrderType.Bid;
expected.price = new BigNumber(0.5); expected.price = new BigNumber(0.5);
// ddex currently confuses base and quote assets. // ddex currently confuses base and quote assets.
// Switch them to maintain our internal consistency. // Switch them to maintain our internal consistency.

View File

@ -6,6 +6,7 @@ import 'mocha';
import { ExchangeFillEvent } from '../../../src/entities'; import { ExchangeFillEvent } from '../../../src/entities';
import { _convertToExchangeFillEvent } from '../../../src/parsers/events/exchange_events'; import { _convertToExchangeFillEvent } from '../../../src/parsers/events/exchange_events';
import { AssetType } from '../../../src/types';
import { chaiSetup } from '../../utils/chai_setup'; import { chaiSetup } from '../../utils/chai_setup';
chaiSetup.configure(); chaiSetup.configure();
@ -62,12 +63,12 @@ describe('exchange_events', () => {
expected.takerFeePaid = new BigNumber('12345'); expected.takerFeePaid = new BigNumber('12345');
expected.orderHash = '0xab12ed2cbaa5615ab690b9da75a46e53ddfcf3f1a68655b5fe0d94c75a1aac4a'; expected.orderHash = '0xab12ed2cbaa5615ab690b9da75a46e53ddfcf3f1a68655b5fe0d94c75a1aac4a';
expected.rawMakerAssetData = '0xf47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; expected.rawMakerAssetData = '0xf47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
expected.makerAssetType = 'erc20'; expected.makerAssetType = AssetType.ERC20;
expected.makerAssetProxyId = '0xf47261b0'; expected.makerAssetProxyId = '0xf47261b0';
expected.makerTokenAddress = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; expected.makerTokenAddress = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
expected.makerTokenId = null; expected.makerTokenId = null;
expected.rawTakerAssetData = '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498'; expected.rawTakerAssetData = '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498';
expected.takerAssetType = 'erc20'; expected.takerAssetType = AssetType.ERC20;
expected.takerAssetProxyId = '0xf47261b0'; expected.takerAssetProxyId = '0xf47261b0';
expected.takerTokenAddress = '0xe41d2489571d322189246dafa5ebde1f4699f498'; expected.takerTokenAddress = '0xe41d2489571d322189246dafa5ebde1f4699f498';
expected.takerTokenId = null; expected.takerTokenId = null;

View File

@ -31,13 +31,13 @@ describe('idex_orders', () => {
user: '0x212345667543456435324564345643453453333', user: '0x212345667543456435324564345643453453333',
}; };
const observedTimestamp: number = Date.now(); const observedTimestamp: number = Date.now();
const orderType: OrderType = 'bid'; const orderType: OrderType = OrderType.Bid;
const source: string = 'idex'; const source: string = 'idex';
const expected = new TokenOrder(); const expected = new TokenOrder();
expected.source = 'idex'; expected.source = 'idex';
expected.observedTimestamp = observedTimestamp; expected.observedTimestamp = observedTimestamp;
expected.orderType = 'bid'; expected.orderType = OrderType.Bid;
expected.price = new BigNumber(0.5); expected.price = new BigNumber(0.5);
expected.baseAssetSymbol = 'ABC'; expected.baseAssetSymbol = 'ABC';
expected.baseAssetAddress = '0x0000000000000000000000000000000000000000'; expected.baseAssetAddress = '0x0000000000000000000000000000000000000000';
@ -65,13 +65,13 @@ describe('idex_orders', () => {
user: '0x212345667543456435324564345643453453333', user: '0x212345667543456435324564345643453453333',
}; };
const observedTimestamp: number = Date.now(); const observedTimestamp: number = Date.now();
const orderType: OrderType = 'ask'; const orderType: OrderType = OrderType.Ask;
const source: string = 'idex'; const source: string = 'idex';
const expected = new TokenOrder(); const expected = new TokenOrder();
expected.source = 'idex'; expected.source = 'idex';
expected.observedTimestamp = observedTimestamp; expected.observedTimestamp = observedTimestamp;
expected.orderType = 'ask'; expected.orderType = OrderType.Ask;
expected.price = new BigNumber(0.5); expected.price = new BigNumber(0.5);
expected.baseAssetSymbol = 'ABC'; expected.baseAssetSymbol = 'ABC';
expected.baseAssetAddress = '0x0000000000000000000000000000000000000000'; expected.baseAssetAddress = '0x0000000000000000000000000000000000000000';

View File

@ -27,13 +27,13 @@ describe('oasis_orders', () => {
low: 0, low: 0,
}; };
const observedTimestamp: number = Date.now(); const observedTimestamp: number = Date.now();
const orderType: OrderType = 'bid'; const orderType: OrderType = OrderType.Bid;
const source: string = 'oasis'; const source: string = 'oasis';
const expected = new TokenOrder(); const expected = new TokenOrder();
expected.source = 'oasis'; expected.source = 'oasis';
expected.observedTimestamp = observedTimestamp; expected.observedTimestamp = observedTimestamp;
expected.orderType = 'bid'; expected.orderType = OrderType.Bid;
expected.price = new BigNumber(0.5); expected.price = new BigNumber(0.5);
expected.baseAssetSymbol = 'DEF'; expected.baseAssetSymbol = 'DEF';
expected.baseAssetAddress = null; expected.baseAssetAddress = null;

View File

@ -32,13 +32,13 @@ describe('paradex_orders', () => {
quoteTokenAddress: '0x0000000000000000000000000000000000000000', quoteTokenAddress: '0x0000000000000000000000000000000000000000',
}; };
const observedTimestamp: number = Date.now(); const observedTimestamp: number = Date.now();
const orderType: OrderType = 'bid'; const orderType: OrderType = OrderType.Bid;
const source: string = 'paradex'; const source: string = 'paradex';
const expected = new TokenOrder(); const expected = new TokenOrder();
expected.source = 'paradex'; expected.source = 'paradex';
expected.observedTimestamp = observedTimestamp; expected.observedTimestamp = observedTimestamp;
expected.orderType = 'bid'; expected.orderType = OrderType.Bid;
expected.price = new BigNumber(0.1245); expected.price = new BigNumber(0.1245);
expected.baseAssetSymbol = 'DEF'; expected.baseAssetSymbol = 'DEF';
expected.baseAssetAddress = '0xb45df06e38540a675fdb5b598abf2c0dbe9d6b81'; expected.baseAssetAddress = '0xb45df06e38540a675fdb5b598abf2c0dbe9d6b81';

View File

@ -5,6 +5,7 @@ import 'mocha';
import { SraOrder } from '../../../src/entities'; import { SraOrder } from '../../../src/entities';
import { _convertToEntity } from '../../../src/parsers/sra_orders'; import { _convertToEntity } from '../../../src/parsers/sra_orders';
import { AssetType } from '../../../src/types';
import { chaiSetup } from '../../utils/chai_setup'; import { chaiSetup } from '../../utils/chai_setup';
chaiSetup.configure(); chaiSetup.configure();
@ -50,12 +51,12 @@ describe('sra_orders', () => {
expected.signature = expected.signature =
'0x1b5a5d672b0d647b5797387ccbb89d822d5d2e873346b014f4ff816ff0783f2a7a0d2824d2d7042ec8ea375bc7f870963e1cb8248f1db03ddf125e27b5963aa11f03'; '0x1b5a5d672b0d647b5797387ccbb89d822d5d2e873346b014f4ff816ff0783f2a7a0d2824d2d7042ec8ea375bc7f870963e1cb8248f1db03ddf125e27b5963aa11f03';
expected.rawMakerAssetData = '0xf47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; expected.rawMakerAssetData = '0xf47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
expected.makerAssetType = 'erc20'; expected.makerAssetType = AssetType.ERC20;
expected.makerAssetProxyId = '0xf47261b0'; expected.makerAssetProxyId = '0xf47261b0';
expected.makerTokenAddress = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; expected.makerTokenAddress = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
expected.makerTokenId = null; expected.makerTokenId = null;
expected.rawTakerAssetData = '0xf47261b000000000000000000000000042d6622dece394b54999fbd73d108123806f6a18'; expected.rawTakerAssetData = '0xf47261b000000000000000000000000042d6622dece394b54999fbd73d108123806f6a18';
expected.takerAssetType = 'erc20'; expected.takerAssetType = AssetType.ERC20;
expected.takerAssetProxyId = '0xf47261b0'; expected.takerAssetProxyId = '0xf47261b0';
expected.takerTokenAddress = '0x42d6622dece394b54999fbd73d108123806f6a18'; expected.takerTokenAddress = '0x42d6622dece394b54999fbd73d108123806f6a18';
expected.takerTokenId = null; expected.takerTokenId = null;

View File

@ -18,6 +18,10 @@
{ {
"note": "Add RevertReasons for DutchAuction contract", "note": "Add RevertReasons for DutchAuction contract",
"pr": 1225 "pr": 1225
},
{
"note": "Add MultiAsset types",
"pr": 1363
} }
], ],
"timestamp": 1544570656 "timestamp": 1544570656

View File

@ -110,7 +110,9 @@ export type DoneCallback = (err?: Error) => void;
export interface OrderRelevantState { export interface OrderRelevantState {
makerBalance: BigNumber; makerBalance: BigNumber;
makerIndividualBalances: ObjectMap<BigNumber>;
makerProxyAllowance: BigNumber; makerProxyAllowance: BigNumber;
makerIndividualProxyAllowances: ObjectMap<BigNumber>;
makerFeeBalance: BigNumber; makerFeeBalance: BigNumber;
makerFeeProxyAllowance: BigNumber; makerFeeProxyAllowance: BigNumber;
filledTakerAssetAmount: BigNumber; filledTakerAssetAmount: BigNumber;
@ -155,6 +157,7 @@ export enum SignatureType {
export enum AssetProxyId { export enum AssetProxyId {
ERC20 = '0xf47261b0', ERC20 = '0xf47261b0',
ERC721 = '0x02571792', ERC721 = '0x02571792',
MultiAsset = '0x94cfcdd7',
} }
export interface ERC20AssetData { export interface ERC20AssetData {
@ -168,7 +171,21 @@ export interface ERC721AssetData {
tokenId: BigNumber; tokenId: BigNumber;
} }
export type AssetData = ERC20AssetData | ERC721AssetData; export type SingleAssetData = ERC20AssetData | ERC721AssetData;
export interface MultiAssetData {
assetProxyId: string;
amounts: BigNumber[];
nestedAssetData: string[];
}
export interface MultiAssetDataWithRecursiveDecoding {
assetProxyId: string;
amounts: BigNumber[];
nestedAssetData: SingleAssetData[];
}
export type AssetData = SingleAssetData | MultiAssetData | MultiAssetDataWithRecursiveDecoding;
// TODO: DRY. These should be extracted from contract code. // TODO: DRY. These should be extracted from contract code.
export enum RevertReason { export enum RevertReason {

View File

@ -1,4 +1,13 @@
[ [
{
"version": "2.1.1",
"changes": [
{
"note": "Add `should` prefix to names of properties in EncodingRules and DecodingRules",
"pr": 1363
}
]
},
{ {
"version": "2.1.0", "version": "2.1.0",
"changes": [ "changes": [

View File

@ -62,7 +62,7 @@ export abstract class AbstractSetDataType extends DataType {
// Create a new scope in the calldata, before descending into the members of this set. // Create a new scope in the calldata, before descending into the members of this set.
calldata.startScope(); calldata.startScope();
let value: any[] | object; let value: any[] | object;
if (rules.structsAsObjects && !this._isArray) { if (rules.shouldConvertStructsToObjects && !this._isArray) {
// Construct an object with values for each member of the set. // Construct an object with values for each member of the set.
value = {}; value = {};
_.each(this._memberIndexByName, (idx: number, key: string) => { _.each(this._memberIndexByName, (idx: number, key: string) => {

View File

@ -49,7 +49,7 @@ export class Calldata {
throw new Error('expected root'); throw new Error('expected root');
} }
// Optimize, if flag set // Optimize, if flag set
if (this._rules.optimize) { if (this._rules.shouldOptimize) {
this._optimize(); this._optimize();
} }
// Set offsets // Set offsets
@ -60,7 +60,9 @@ export class Calldata {
offset += block.getSizeInBytes(); offset += block.getSizeInBytes();
} }
// Generate hex string // Generate hex string
const hexString = this._rules.annotate ? this._toHumanReadableCallData() : this._toEvmCompatibeCallDataHex(); const hexString = this._rules.shouldAnnotate
? this._toHumanReadableCallData()
: this._toEvmCompatibeCallDataHex();
return hexString; return hexString;
} }
/** /**

View File

@ -11,7 +11,7 @@ export const constants = {
HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA: 0, HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA: 0,
// Disable no-object-literal-type-assertion so we can enforce cast // Disable no-object-literal-type-assertion so we can enforce cast
/* tslint:disable no-object-literal-type-assertion */ /* tslint:disable no-object-literal-type-assertion */
DEFAULT_DECODING_RULES: { structsAsObjects: false } as DecodingRules, DEFAULT_DECODING_RULES: { shouldConvertStructsToObjects: false } as DecodingRules,
DEFAULT_ENCODING_RULES: { optimize: true, annotate: false } as EncodingRules, DEFAULT_ENCODING_RULES: { shouldOptimize: true, shouldAnnotate: false } as EncodingRules,
/* tslint:enable no-object-literal-type-assertion */ /* tslint:enable no-object-literal-type-assertion */
}; };

View File

@ -1,8 +1,8 @@
export interface DecodingRules { export interface DecodingRules {
structsAsObjects: boolean; shouldConvertStructsToObjects: boolean;
} }
export interface EncodingRules { export interface EncodingRules {
optimize?: boolean; shouldOptimize?: boolean;
annotate?: boolean; shouldAnnotate?: boolean;
} }

View File

@ -10,7 +10,7 @@ chaiSetup.configure();
const expect = chai.expect; const expect = chai.expect;
describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => {
const encodingRules: AbiEncoder.EncodingRules = { optimize: false }; // optimizer is tested separately. const encodingRules: AbiEncoder.EncodingRules = { shouldOptimize: false }; // optimizer is tested separately.
describe('Array', () => { describe('Array', () => {
it('Fixed size; Static elements', async () => { it('Fixed size; Static elements', async () => {
// Create DataType object // Create DataType object
@ -194,7 +194,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => {
'0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000001'; '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000001';
expect(encodedArgs).to.be.equal(expectedEncodedArgs); expect(encodedArgs).to.be.equal(expectedEncodedArgs);
// Decode Encoded Args and validate result // Decode Encoded Args and validate result
const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodingRules: AbiEncoder.DecodingRules = { shouldConvertStructsToObjects: true };
const decodedArgs = dataType.decode(encodedArgs, decodingRules); const decodedArgs = dataType.decode(encodedArgs, decodingRules);
expect(decodedArgs).to.be.deep.equal(args); expect(decodedArgs).to.be.deep.equal(args);
}); });
@ -214,7 +214,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => {
'0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008abcdef0123456789000000000000000000000000000000000000000000000000'; '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008abcdef0123456789000000000000000000000000000000000000000000000000';
expect(encodedArgs).to.be.equal(expectedEncodedArgs); expect(encodedArgs).to.be.equal(expectedEncodedArgs);
// Decode Encoded Args and validate result // Decode Encoded Args and validate result
const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodingRules: AbiEncoder.DecodingRules = { shouldConvertStructsToObjects: true };
const decodedArgs = dataType.decode(encodedArgs, decodingRules); const decodedArgs = dataType.decode(encodedArgs, decodingRules);
expect(decodedArgs).to.be.deep.equal(args); expect(decodedArgs).to.be.deep.equal(args);
}); });
@ -234,7 +234,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => {
'0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002'; '0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002';
expect(encodedArgs).to.be.equal(expectedEncodedArgs); expect(encodedArgs).to.be.equal(expectedEncodedArgs);
// Decode Encoded Args and validate result // Decode Encoded Args and validate result
const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodingRules: AbiEncoder.DecodingRules = { shouldConvertStructsToObjects: true };
const decodedArgs = dataType.decode(encodedArgs, decodingRules); const decodedArgs = dataType.decode(encodedArgs, decodingRules);
expect(decodedArgs).to.be.deep.equal(args); expect(decodedArgs).to.be.deep.equal(args);
}); });
@ -254,7 +254,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => {
'0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002'; '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002';
expect(encodedArgs).to.be.equal(expectedEncodedArgs); expect(encodedArgs).to.be.equal(expectedEncodedArgs);
// Decode Encoded Args and validate result // Decode Encoded Args and validate result
const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodingRules: AbiEncoder.DecodingRules = { shouldConvertStructsToObjects: true };
const decodedArgs = dataType.decode(encodedArgs, decodingRules); const decodedArgs = dataType.decode(encodedArgs, decodingRules);
expect(decodedArgs).to.be.deep.equal(args); expect(decodedArgs).to.be.deep.equal(args);
}); });
@ -276,7 +276,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => {
'0x0102030400000000000000000000000000000000000000000000000000000000050607080000000000000000000000000000000000000000000000000000000009101112000000000000000000000000000000000000000000000000000000001314151600000000000000000000000000000000000000000000000000000000'; '0x0102030400000000000000000000000000000000000000000000000000000000050607080000000000000000000000000000000000000000000000000000000009101112000000000000000000000000000000000000000000000000000000001314151600000000000000000000000000000000000000000000000000000000';
expect(encodedArgs).to.be.equal(expectedEncodedArgs); expect(encodedArgs).to.be.equal(expectedEncodedArgs);
// Decode Encoded Args and validate result // Decode Encoded Args and validate result
const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodingRules: AbiEncoder.DecodingRules = { shouldConvertStructsToObjects: true };
const decodedArgs = dataType.decode(encodedArgs, decodingRules); const decodedArgs = dataType.decode(encodedArgs, decodingRules);
expect(decodedArgs).to.be.deep.equal(args); expect(decodedArgs).to.be.deep.equal(args);
}); });
@ -298,7 +298,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => {
'0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004010203040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040506070800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004091011120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041314151600000000000000000000000000000000000000000000000000000000'; '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004010203040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040506070800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004091011120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041314151600000000000000000000000000000000000000000000000000000000';
expect(encodedArgs).to.be.equal(expectedEncodedArgs); expect(encodedArgs).to.be.equal(expectedEncodedArgs);
// Decode Encoded Args and validate result // Decode Encoded Args and validate result
const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodingRules: AbiEncoder.DecodingRules = { shouldConvertStructsToObjects: true };
const decodedArgs = dataType.decode(encodedArgs, decodingRules); const decodedArgs = dataType.decode(encodedArgs, decodingRules);
expect(decodedArgs).to.be.deep.equal(args); expect(decodedArgs).to.be.deep.equal(args);
}); });
@ -328,7 +328,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => {
'0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008abcdef0123456789000000000000000000000000000000000000000000000000'; '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008abcdef0123456789000000000000000000000000000000000000000000000000';
expect(encodedArgs).to.be.equal(expectedEncodedArgs); expect(encodedArgs).to.be.equal(expectedEncodedArgs);
// Decode Encoded Args and validate result // Decode Encoded Args and validate result
const decodingRules: AbiEncoder.DecodingRules = { structsAsObjects: true }; const decodingRules: AbiEncoder.DecodingRules = { shouldConvertStructsToObjects: true };
const decodedArgs = dataType.decode(encodedArgs, decodingRules); const decodedArgs = dataType.decode(encodedArgs, decodingRules);
expect(decodedArgs).to.be.deep.equal(args); expect(decodedArgs).to.be.deep.equal(args);
}); });

File diff suppressed because one or more lines are too long

View File

@ -10,7 +10,7 @@ chaiSetup.configure();
const expect = chai.expect; const expect = chai.expect;
describe('ABI Encoder: Optimized Method Encoding/Decoding', () => { describe('ABI Encoder: Optimized Method Encoding/Decoding', () => {
const encodingRules: AbiEncoder.EncodingRules = { optimize: true }; const encodingRules: AbiEncoder.EncodingRules = { shouldOptimize: true };
it('Duplicate Dynamic Arrays with Static Elements', async () => { it('Duplicate Dynamic Arrays with Static Elements', async () => {
// Generate calldata // Generate calldata
const method = new AbiEncoder.Method(OptimizedAbis.duplicateDynamicArraysWithStaticElements); const method = new AbiEncoder.Method(OptimizedAbis.duplicateDynamicArraysWithStaticElements);
@ -206,7 +206,7 @@ describe('ABI Encoder: Optimized Method Encoding/Decoding', () => {
const twoDimArray2 = twoDimArray1; const twoDimArray2 = twoDimArray1;
const args = [twoDimArray1, twoDimArray2]; const args = [twoDimArray1, twoDimArray2];
// Validata calldata // Validata calldata
const optimizedCalldata = method.encode(args, { optimize: false }); const optimizedCalldata = method.encode(args, { shouldOptimize: false });
const expectedOptimizedCalldata = const expectedOptimizedCalldata =
'0x0d28c4f9000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003466f6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003426172000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035a61610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003466f6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003426172000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035a61610000000000000000000000000000000000000000000000000000000000'; '0x0d28c4f9000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003466f6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003426172000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035a61610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003466f6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003426172000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035a61610000000000000000000000000000000000000000000000000000000000';
expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata);

View File

@ -10,7 +10,7 @@ chaiSetup.configure();
const expect = chai.expect; const expect = chai.expect;
describe('ABI Encoder: Return Value Encoding/Decoding', () => { describe('ABI Encoder: Return Value Encoding/Decoding', () => {
const encodingRules: AbiEncoder.EncodingRules = { optimize: false }; // optimizer is tested separately. const encodingRules: AbiEncoder.EncodingRules = { shouldOptimize: false }; // optimizer is tested separately.
it('No Return Value', async () => { it('No Return Value', async () => {
// Decode return value // Decode return value
const method = new AbiEncoder.Method(ReturnValueAbis.noReturnValues); const method = new AbiEncoder.Method(ReturnValueAbis.noReturnValues);

View File

@ -507,7 +507,7 @@
lodash "^4.17.5" lodash "^4.17.5"
uuid "^3.1.0" uuid "^3.1.0"
"@0x/order-utils@^2.0.0", "@0x/order-utils@^2.0.1": "@0x/order-utils@^2.0.1":
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/@0x/order-utils/-/order-utils-2.0.1.tgz#8c46d7aeb9e2cce54a0822824c12427cbe5f7eb4" resolved "https://registry.yarnpkg.com/@0x/order-utils/-/order-utils-2.0.1.tgz#8c46d7aeb9e2cce54a0822824c12427cbe5f7eb4"
dependencies: dependencies:
@ -2100,10 +2100,6 @@ aes-js@^0.2.3:
version "0.2.4" version "0.2.4"
resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-0.2.4.tgz#94b881ab717286d015fa219e08fb66709dda5a3d" resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-0.2.4.tgz#94b881ab717286d015fa219e08fb66709dda5a3d"
aes-js@^3.1.1:
version "3.1.2"
resolved "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a"
agent-base@4, agent-base@^4.1.0, agent-base@~4.2.0: agent-base@4, agent-base@^4.1.0, agent-base@~4.2.0:
version "4.2.1" version "4.2.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
@ -3669,7 +3665,7 @@ bs-logger@0.x:
dependencies: dependencies:
fast-json-stable-stringify "^2.0.0" fast-json-stable-stringify "^2.0.0"
bs58@=4.0.1, bs58@^4.0.0: bs58@=4.0.1:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a"
dependencies: dependencies:
@ -3692,14 +3688,6 @@ bs58check@^1.0.8:
bs58 "^3.1.0" bs58 "^3.1.0"
create-hash "^1.1.0" create-hash "^1.1.0"
bs58check@^2.1.2:
version "2.1.2"
resolved "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc"
dependencies:
bs58 "^4.0.0"
create-hash "^1.1.0"
safe-buffer "^5.1.2"
bser@^2.0.0: bser@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" resolved "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719"
@ -6410,19 +6398,6 @@ ethereumjs-wallet@0.6.0:
utf8 "^2.1.1" utf8 "^2.1.1"
uuid "^2.0.1" uuid "^2.0.1"
ethereumjs-wallet@~0.6.0:
version "0.6.2"
resolved "https://registry.npmjs.org/ethereumjs-wallet/-/ethereumjs-wallet-0.6.2.tgz#67244b6af3e8113b53d709124b25477b64aeccda"
dependencies:
aes-js "^3.1.1"
bs58check "^2.1.2"
ethereumjs-util "^5.2.0"
hdkey "^1.0.0"
safe-buffer "^5.1.2"
scrypt.js "^0.2.0"
utf8 "^3.0.0"
uuid "^3.3.2"
ethers@~4.0.4: ethers@~4.0.4:
version "4.0.4" version "4.0.4"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.4.tgz#d3f85e8b27f4b59537e06526439b0fb15b44dc65" resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.4.tgz#d3f85e8b27f4b59537e06526439b0fb15b44dc65"
@ -7339,7 +7314,7 @@ ganache-core@0xProject/ganache-core#monorepo-dep:
ethereumjs-tx "0xProject/ethereumjs-tx#fake-tx-include-signature-by-default" ethereumjs-tx "0xProject/ethereumjs-tx#fake-tx-include-signature-by-default"
ethereumjs-util "^5.2.0" ethereumjs-util "^5.2.0"
ethereumjs-vm "2.3.5" ethereumjs-vm "2.3.5"
ethereumjs-wallet "~0.6.0" ethereumjs-wallet "0.6.0"
fake-merkle-patricia-tree "~1.0.1" fake-merkle-patricia-tree "~1.0.1"
heap "~0.2.6" heap "~0.2.6"
js-scrypt "^0.2.0" js-scrypt "^0.2.0"
@ -8077,14 +8052,6 @@ hdkey@^0.7.0, hdkey@^0.7.1:
coinstring "^2.0.0" coinstring "^2.0.0"
secp256k1 "^3.0.1" secp256k1 "^3.0.1"
hdkey@^1.0.0:
version "1.1.0"
resolved "https://registry.npmjs.org/hdkey/-/hdkey-1.1.0.tgz#e74e7b01d2c47f797fa65d1d839adb7a44639f29"
dependencies:
coinstring "^2.0.0"
safe-buffer "^5.1.1"
secp256k1 "^3.0.1"
he@1.1.1: he@1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
@ -16679,10 +16646,6 @@ utf8@^2.1.1:
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/utf8/-/utf8-2.1.2.tgz#1fa0d9270e9be850d9b05027f63519bf46457d96" resolved "https://registry.yarnpkg.com/utf8/-/utf8-2.1.2.tgz#1fa0d9270e9be850d9b05027f63519bf46457d96"
utf8@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1"
util-deprecate@~1.0.1: util-deprecate@~1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"