@0x/contracts-exchange: Fully incorporate ERC1155 and MultiAsset tests into the fillOrder and matchOrders test suites.

This commit is contained in:
Lawrence Forman 2019-05-24 12:35:47 -04:00 committed by Amir Bandeali
parent 57ac0ca6e8
commit 07e3ba014c
7 changed files with 594 additions and 301 deletions

View File

@ -29,6 +29,14 @@
{ {
"note": "Add support for `SignatureType.WalletOrderValidator` for orders", "note": "Add support for `SignatureType.WalletOrderValidator` for orders",
"pr": 1774 "pr": 1774
},
{
"note": "Remove ZRX fees in lieu of arbitrary maker and taker fee tokens.",
"pr": 1819
},
{
"note": "Incorporate Multi-asset and ERC1155 tests into `fillOrder` and `matchOrders` tests",
"pr": 1819
} }
] ]
}, },

View File

@ -177,6 +177,24 @@ describe('FillOrder Tests', () => {
}); });
describe('ERC20', () => { describe('ERC20', () => {
const assetCombinations = getAllPossiblePairs([
AssetDataScenario.ERC20ZeroDecimals,
AssetDataScenario.ERC20FiveDecimals,
AssetDataScenario.ERC20EighteenDecimals,
]);
for (const [makerAsset, takerAsset] of assetCombinations) {
it(`should transfer correct amounts between ${makerAsset} and ${takerAsset}`, async () => {
const fillScenario = {
...defaultFillScenario,
orderScenario: {
...defaultFillScenario.orderScenario,
makerAssetDataScenario: makerAsset,
takerAssetDataScenario: takerAsset,
},
};
await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario);
});
}
it('should be able to pay maker fee with taker asset', async () => { it('should be able to pay maker fee with taker asset', async () => {
const fillScenario = { const fillScenario = {
...defaultFillScenario, ...defaultFillScenario,
@ -573,14 +591,14 @@ describe('FillOrder Tests', () => {
AssetDataScenario.ERC1155NonFungible, AssetDataScenario.ERC1155NonFungible,
AssetDataScenario.MultiAssetERC20, AssetDataScenario.MultiAssetERC20,
]; ];
for (const [makerAssetData, takerAssetData] of getAllPossiblePairs(assetDataScenarios)) { for (const [makerAsset, takerAsset] of getAllPossiblePairs(assetDataScenarios)) {
it(`should successfully exchange ${makerAssetData} for ${takerAssetData}`, async () => { it(`should successfully exchange ${makerAsset} for ${takerAsset}`, async () => {
const fillScenario = { const fillScenario = {
...defaultFillScenario, ...defaultFillScenario,
orderScenario: { orderScenario: {
...defaultFillScenario.orderScenario, ...defaultFillScenario.orderScenario,
makerAssetDataScenario: makerAssetData, makerAssetDataScenario: makerAsset,
takerAssetDataScenario: takerAssetData, takerAssetDataScenario: takerAsset,
}, },
takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount,
}; };
@ -590,21 +608,21 @@ describe('FillOrder Tests', () => {
}); });
describe('Maker/taker fee asset combinations', () => { describe('Maker/taker fee asset combinations', () => {
const feeAssetDataScenarios = [ const feeAssetPairs = getAllPossiblePairs([
FeeAssetDataScenario.ERC20EighteenDecimals, FeeAssetDataScenario.ERC20EighteenDecimals,
FeeAssetDataScenario.ERC721, FeeAssetDataScenario.ERC721,
FeeAssetDataScenario.ERC1155Fungible, FeeAssetDataScenario.ERC1155Fungible,
FeeAssetDataScenario.ERC1155NonFungible, FeeAssetDataScenario.ERC1155NonFungible,
FeeAssetDataScenario.MultiAssetERC20, FeeAssetDataScenario.MultiAssetERC20,
]; ]);
for (const [makerFeeAssetData, takerFeeAssetData] of getAllPossiblePairs(feeAssetDataScenarios)) { for (const [makerFeeAsset, takerFeeAsset] of feeAssetPairs) {
it(`should successfully pay maker fee ${makerFeeAssetData} and taker fee ${takerFeeAssetData}`, async () => { it(`should successfully pay maker fee ${makerFeeAsset} and taker fee ${takerFeeAsset}`, async () => {
const fillScenario = { const fillScenario = {
...defaultFillScenario, ...defaultFillScenario,
orderScenario: { orderScenario: {
...defaultFillScenario.orderScenario, ...defaultFillScenario.orderScenario,
makerFeeAssetDataScenario: makerFeeAssetData, makerFeeAssetDataScenario: makerFeeAsset,
takerFeeAssetDataScenario: takerFeeAssetData, takerFeeAssetDataScenario: takerFeeAsset,
}, },
takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount,
}; };

View File

@ -14,8 +14,6 @@ import { DummyERC721TokenContract } from '@0x/contracts-erc721';
import { import {
chaiSetup, chaiSetup,
constants, constants,
ERC1155HoldingsByOwner,
ERC721TokenIdsByOwner,
OrderFactory, OrderFactory,
provider, provider,
txDefaults, txDefaults,
@ -34,17 +32,14 @@ import {
constants as exchangeConstants, constants as exchangeConstants,
ExchangeContract, ExchangeContract,
ExchangeWrapper, ExchangeWrapper,
MatchOrderTester,
ReentrantERC20TokenContract, ReentrantERC20TokenContract,
TestExchangeInternalsContract, TestExchangeInternalsContract,
} from '../src'; } from '../src';
interface IndividualERC1155Holdings { import { MatchOrderTester, TokenBalances } from './utils/match_order_tester';
fungible: {
[tokenId: string]: BigNumber; const ONE = new BigNumber(1);
}; const TWO = new BigNumber(2);
nonFungible: BigNumber[];
}
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
chaiSetup.configure(); chaiSetup.configure();
@ -76,14 +71,12 @@ describe('matchOrders', () => {
let orderFactoryLeft: OrderFactory; let orderFactoryLeft: OrderFactory;
let orderFactoryRight: OrderFactory; let orderFactoryRight: OrderFactory;
let erc721LeftMakerAssetIds: BigNumber[]; let tokenBalances: TokenBalances;
let erc721RightMakerAssetIds: BigNumber[];
let erc1155LeftMakerHoldings: IndividualERC1155Holdings;
let erc1155RightMakerHoldings: IndividualERC1155Holdings;
let defaultERC20MakerAssetAddress: string; let defaultERC20MakerAssetAddress: string;
let defaultERC20TakerAssetAddress: string; let defaultERC20TakerAssetAddress: string;
let defaultERC721AssetAddress: string; let defaultERC721AssetAddress: string;
let defaultERC1155AssetAddress: string;
let defaultFeeTokenAddress: string; let defaultFeeTokenAddress: string;
let matchOrderTester: MatchOrderTester; let matchOrderTester: MatchOrderTester;
@ -109,41 +102,25 @@ describe('matchOrders', () => {
feeRecipientAddressLeft, feeRecipientAddressLeft,
feeRecipientAddressRight, feeRecipientAddressRight,
] = accounts); ] = accounts);
const addressesWithBalances = usedAddresses.slice(1);
// Create wrappers // Create wrappers
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); erc20Wrapper = new ERC20Wrapper(provider, addressesWithBalances, owner);
erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); erc721Wrapper = new ERC721Wrapper(provider, addressesWithBalances, owner);
erc1155ProxyWrapper = new ERC1155ProxyWrapper(provider, usedAddresses, owner); erc1155ProxyWrapper = new ERC1155ProxyWrapper(provider, addressesWithBalances, owner);
// Deploy ERC20 token & ERC20 proxy // Deploy ERC20 token & ERC20 proxy
const numDummyErc20ToDeploy = 4; const numDummyErc20ToDeploy = 4;
erc20Tokens = await erc20Wrapper.deployDummyTokensAsync( erc20Tokens = await erc20Wrapper.deployDummyTokensAsync(numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS);
numDummyErc20ToDeploy,
constants.DUMMY_TOKEN_DECIMALS,
);
erc20Proxy = await erc20Wrapper.deployProxyAsync(); erc20Proxy = await erc20Wrapper.deployProxyAsync();
await erc20Wrapper.setBalancesAndAllowancesAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync();
// Deploy ERC721 token and proxy // Deploy ERC721 token and proxy
[erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); [erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
erc721Proxy = await erc721Wrapper.deployProxyAsync(); erc721Proxy = await erc721Wrapper.deployProxyAsync();
await erc721Wrapper.setBalancesAndAllowancesAsync(); await erc721Wrapper.setBalancesAndAllowancesAsync();
const erc721Balances = await erc721Wrapper.getBalancesAsync();
erc721LeftMakerAssetIds = erc721Balances[makerAddressLeft][erc721Token.address];
erc721RightMakerAssetIds = erc721Balances[makerAddressRight][erc721Token.address];
// Deploy ERC1155 token and proxy // Deploy ERC1155 token and proxy
[erc1155Wrapper] = await erc1155ProxyWrapper.deployDummyContractsAsync(); [erc1155Wrapper] = await erc1155ProxyWrapper.deployDummyContractsAsync();
erc1155Token = erc1155Wrapper.getContract(); erc1155Token = erc1155Wrapper.getContract();
erc1155Proxy = await erc1155ProxyWrapper.deployProxyAsync(); erc1155Proxy = await erc1155ProxyWrapper.deployProxyAsync();
await erc1155ProxyWrapper.setBalancesAndAllowancesAsync(); await erc1155ProxyWrapper.setBalancesAndAllowancesAsync();
const erc1155Holdings = await erc1155ProxyWrapper.getBalancesAsync();
erc1155LeftMakerHoldings = getIndividualERC1155Holdings(
erc1155Holdings,
erc1155Token.address,
makerAddressLeft,
);
erc1155RightMakerHoldings = getIndividualERC1155Holdings(
erc1155Holdings,
erc1155Token.address,
makerAddressRight,
);
// Deploy MultiAssetProxy. // Deploy MultiAssetProxy.
const multiAssetProxyContract = await MultiAssetProxyContract.deployFrom0xArtifactAsync( const multiAssetProxyContract = await MultiAssetProxyContract.deployFrom0xArtifactAsync(
assetProxyArtifacts.MultiAssetProxy, assetProxyArtifacts.MultiAssetProxy,
@ -178,6 +155,11 @@ describe('matchOrders', () => {
{ from: owner }, { from: owner },
constants.AWAIT_TRANSACTION_MINED_MS, constants.AWAIT_TRANSACTION_MINED_MS,
); );
await multiAssetProxyContract.addAuthorizedAddress.awaitTransactionSuccessAsync(
exchange.address,
{ from: owner },
constants.AWAIT_TRANSACTION_MINED_MS,
);
await erc20Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync( await erc20Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync(
multiAssetProxyContract.address, multiAssetProxyContract.address,
{ from: owner }, { from: owner },
@ -193,8 +175,18 @@ describe('matchOrders', () => {
{ from: owner }, { from: owner },
constants.AWAIT_TRANSACTION_MINED_MS, constants.AWAIT_TRANSACTION_MINED_MS,
); );
await multiAssetProxyContract.addAuthorizedAddress.awaitTransactionSuccessAsync( await multiAssetProxyContract.registerAssetProxy.awaitTransactionSuccessAsync(
exchange.address, erc20Proxy.address,
{ from: owner },
constants.AWAIT_TRANSACTION_MINED_MS,
);
await multiAssetProxyContract.registerAssetProxy.awaitTransactionSuccessAsync(
erc721Proxy.address,
{ from: owner },
constants.AWAIT_TRANSACTION_MINED_MS,
);
await multiAssetProxyContract.registerAssetProxy.awaitTransactionSuccessAsync(
erc1155Proxy.address,
{ from: owner }, { from: owner },
constants.AWAIT_TRANSACTION_MINED_MS, constants.AWAIT_TRANSACTION_MINED_MS,
); );
@ -211,6 +203,7 @@ describe('matchOrders', () => {
defaultERC20TakerAssetAddress = erc20Tokens[1].address; defaultERC20TakerAssetAddress = erc20Tokens[1].address;
defaultFeeTokenAddress = erc20Tokens[2].address; defaultFeeTokenAddress = erc20Tokens[2].address;
defaultERC721AssetAddress = erc721Token.address; defaultERC721AssetAddress = erc721Token.address;
defaultERC1155AssetAddress = erc1155Token.address;
const domain = { const domain = {
verifyingContractAddress: exchange.address, verifyingContractAddress: exchange.address,
chainId, chainId,
@ -240,17 +233,15 @@ describe('matchOrders', () => {
orderFactoryLeft = new OrderFactory(privateKeyLeft, defaultOrderParamsLeft); orderFactoryLeft = new OrderFactory(privateKeyLeft, defaultOrderParamsLeft);
const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)]; const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)];
orderFactoryRight = new OrderFactory(privateKeyRight, defaultOrderParamsRight); orderFactoryRight = new OrderFactory(privateKeyRight, defaultOrderParamsRight);
// Set match order tester
matchOrderTester = new MatchOrderTester(exchangeWrapper, erc20Wrapper, erc721Wrapper);
testExchange = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( testExchange = await TestExchangeInternalsContract.deployFrom0xArtifactAsync(
artifacts.TestExchangeInternals, artifacts.TestExchangeInternals,
provider, provider,
txDefaults, txDefaults,
new BigNumber(chainId), new BigNumber(chainId),
); );
// Create match order tester
console.log(erc1155LeftMakerHoldings); matchOrderTester = new MatchOrderTester(exchangeWrapper, erc20Wrapper, erc721Wrapper, erc1155ProxyWrapper);
console.log(erc1155RightMakerHoldings); tokenBalances = await matchOrderTester.getBalancesAsync();
}); });
beforeEach(async () => { beforeEach(async () => {
await blockchainLifecycle.startAsync(); await blockchainLifecycle.startAsync();
@ -1266,267 +1257,452 @@ describe('matchOrders', () => {
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
}); });
it('should transfer correct amounts when left order maker asset is an ERC721 token', async () => { describe('combinations', () => {
// Create orders to match
const erc721TokenToTransfer = erc721LeftMakerAssetIds[0];
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
makerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer),
makerAssetAmount: new BigNumber(1),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18),
});
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
takerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer),
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18),
takerAssetAmount: new BigNumber(1),
});
// Match orders
const expectedTransferAmounts = {
// Left Maker
leftMakerAssetSoldByLeftMakerAmount: Web3Wrapper.toBaseUnitAmount(1, 0),
leftMakerFeeAssetPaidByLeftMakerAmount: Web3Wrapper.toBaseUnitAmount(100, 16), // 100%
// Right Maker
rightMakerAssetSoldByRightMakerAmount: Web3Wrapper.toBaseUnitAmount(10, 18),
rightMakerFeeAssetPaidByRightMakerAmount: Web3Wrapper.toBaseUnitAmount(100, 16), // 100%
// Taker
leftTakerFeeAssetPaidByTakerAmount: Web3Wrapper.toBaseUnitAmount(100, 16), // 100%
rightTakerFeeAssetPaidByTakerAmount: Web3Wrapper.toBaseUnitAmount(100, 16), // 50%
};
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
{
leftOrder: signedOrderLeft,
rightOrder: signedOrderRight,
},
takerAddress,
expectedTransferAmounts,
);
});
it('should transfer correct amounts when right order maker asset is an ERC721 token', async () => {
// Create orders to match
const erc721TokenToTransfer = erc721RightMakerAssetIds[0];
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
takerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer),
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18),
takerAssetAmount: new BigNumber(1),
});
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
makerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer),
makerAssetAmount: new BigNumber(1),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(8, 18),
});
// Match orders
const expectedTransferAmounts = {
// Left Maker
leftMakerAssetSoldByLeftMakerAmount: Web3Wrapper.toBaseUnitAmount(10, 18),
leftMakerFeeAssetPaidByLeftMakerAmount: Web3Wrapper.toBaseUnitAmount(100, 16), // 100%
// Right Maker
rightMakerAssetSoldByRightMakerAmount: Web3Wrapper.toBaseUnitAmount(1, 0),
leftMakerAssetBoughtByRightMakerAmount: Web3Wrapper.toBaseUnitAmount(8, 18),
rightMakerFeeAssetPaidByRightMakerAmount: Web3Wrapper.toBaseUnitAmount(100, 16), // 100%
// Taker
leftMakerAssetReceivedByTakerAmount: Web3Wrapper.toBaseUnitAmount(2, 18),
leftTakerFeeAssetPaidByTakerAmount: Web3Wrapper.toBaseUnitAmount(100, 16), // 100%
rightTakerFeeAssetPaidByTakerAmount: Web3Wrapper.toBaseUnitAmount(100, 16), // 100%
};
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
{
leftOrder: signedOrderLeft,
rightOrder: signedOrderRight,
},
takerAddress,
expectedTransferAmounts,
);
});
describe('fee tokens', () => {
// tslint:disable: enum-naming // tslint:disable: enum-naming
enum TokenType { enum AssetType {
ERC20A = 'ERC20A', ERC20A = 'ERC20_A',
ERC20B = 'ERC20B', ERC20B = 'ERC20_B',
ERC20C = 'ERC20C', ERC20C = 'ERC20_C',
ERC20D = 'ERC20D', ERC20D = 'ERC20_D',
ERC721A = 'ERC721A', ERC721LeftMaker = 'ERC721_LEFT_MAKER',
ERC721B = 'ERC721B', ERC721RightMaker = 'ERC721_RIGHT_MAKER',
ERC721C = 'ERC721C', ERC721Taker = 'ERC721_TAKER',
ERC721D = 'ERC721D', ERC1155FungibleA = 'ERC1155_FUNGIBLE_A',
ERC1155FungibleB = 'ERC1155_FUNGIBLE_B',
ERC1155FungibleC = 'ERC1155_FUNGIBLE_C',
ERC1155FungibleD = 'ERC1155_FUNGIBLE_D',
ERC1155NonFungibleLeftMaker = 'ERC1155_NON_FUNGIBLE_LEFT_MAKER',
ERC1155NonFungibleRightMaker = 'ERC1155_NON_FUNGIBLE_RIGHT_MAKER',
ERC1155NonFungibleTaker = 'ERC1155_NON_FUNGIBLE_TAKER',
MultiAssetA = 'MULTI_ASSET_A',
MultiAssetB = 'MULTI_ASSET_B',
MultiAssetC = 'MULTI_ASSET_C',
MultiAssetD = 'MULTI_ASSET_D',
} }
const fungibleTypes = [
AssetType.ERC20A,
AssetType.ERC20B,
AssetType.ERC20C,
AssetType.ERC20D,
AssetType.ERC1155FungibleA,
AssetType.ERC1155FungibleB,
AssetType.ERC1155FungibleC,
AssetType.ERC1155FungibleD,
AssetType.MultiAssetA,
AssetType.MultiAssetB,
AssetType.MultiAssetC,
AssetType.MultiAssetD,
];
interface AssetCombination { interface AssetCombination {
leftMaker: TokenType; leftMaker: AssetType;
rightMaker: TokenType; rightMaker: AssetType;
leftMakerFee: TokenType; leftMakerFee: AssetType;
rightMakerFee: TokenType; rightMakerFee: AssetType;
leftTakerFee: TokenType; leftTakerFee: AssetType;
rightTakerFee: TokenType; rightTakerFee: AssetType;
description?: string; description?: string;
shouldFail?: boolean; shouldFail?: boolean;
} }
const feeAssetCombinations: AssetCombination[] = [ const assetCombinations: AssetCombination[] = [
{ {
description: 'Swapping tokens then using them to pay maker fees.', leftMaker: AssetType.ERC20A,
leftMaker: TokenType.ERC20A, rightMaker: AssetType.ERC20B,
rightMaker: TokenType.ERC20B, leftMakerFee: AssetType.ERC20C,
leftMakerFee: TokenType.ERC20B, rightMakerFee: AssetType.ERC20C,
rightMakerFee: TokenType.ERC20A, leftTakerFee: AssetType.ERC20C,
leftTakerFee: TokenType.ERC20C, rightTakerFee: AssetType.ERC20C,
rightTakerFee: TokenType.ERC20C,
}, },
{ {
description: 'Swapping tokens then using them to pay taker fees.', leftMaker: AssetType.ERC721LeftMaker,
leftMaker: TokenType.ERC20A, rightMaker: AssetType.ERC721RightMaker,
rightMaker: TokenType.ERC20B, leftMakerFee: AssetType.ERC20C,
leftMakerFee: TokenType.ERC20C, rightMakerFee: AssetType.ERC20C,
rightMakerFee: TokenType.ERC20C, leftTakerFee: AssetType.ERC20C,
leftTakerFee: TokenType.ERC20B, rightTakerFee: AssetType.ERC20C,
rightTakerFee: TokenType.ERC20A,
}, },
{ {
description: 'Swapping tokens then using them to pay maker and taker fees.', leftMaker: AssetType.ERC721LeftMaker,
leftMaker: TokenType.ERC20A, rightMaker: AssetType.ERC20A,
rightMaker: TokenType.ERC20B, leftMakerFee: AssetType.ERC20C,
leftMakerFee: TokenType.ERC20B, rightMakerFee: AssetType.ERC20C,
rightMakerFee: TokenType.ERC20A, leftTakerFee: AssetType.ERC20C,
leftTakerFee: TokenType.ERC20C, rightTakerFee: AssetType.ERC20C,
rightTakerFee: TokenType.ERC20C,
}, },
{ {
description: 'Paying maker and taker fees with same tokens being sold.', leftMaker: AssetType.ERC20A,
leftMaker: TokenType.ERC20A, rightMaker: AssetType.ERC721RightMaker,
rightMaker: TokenType.ERC20B, leftMakerFee: AssetType.ERC20C,
leftMakerFee: TokenType.ERC20A, rightMakerFee: AssetType.ERC20C,
rightMakerFee: TokenType.ERC20B, leftTakerFee: AssetType.ERC20C,
leftTakerFee: TokenType.ERC20A, rightTakerFee: AssetType.ERC20C,
rightTakerFee: TokenType.ERC20B,
}, },
{ {
description: 'Paying maker and taker fees with same tokens being bought.', leftMaker: AssetType.ERC1155FungibleA,
leftMaker: TokenType.ERC20A, rightMaker: AssetType.ERC20A,
rightMaker: TokenType.ERC20B, leftMakerFee: AssetType.ERC20C,
leftMakerFee: TokenType.ERC20B, rightMakerFee: AssetType.ERC20C,
rightMakerFee: TokenType.ERC20A, leftTakerFee: AssetType.ERC20C,
leftTakerFee: TokenType.ERC20B, rightTakerFee: AssetType.ERC20C,
rightTakerFee: TokenType.ERC20A,
}, },
{ {
description: 'Buy an ERC721 then use it to pay maker fees.', leftMaker: AssetType.ERC20A,
leftMaker: TokenType.ERC20A, rightMaker: AssetType.ERC1155FungibleB,
rightMaker: TokenType.ERC721A, leftMakerFee: AssetType.ERC20C,
leftMakerFee: TokenType.ERC721A, rightMakerFee: AssetType.ERC20C,
rightMakerFee: TokenType.ERC20B, leftTakerFee: AssetType.ERC20C,
leftTakerFee: TokenType.ERC20C, rightTakerFee: AssetType.ERC20C,
rightTakerFee: TokenType.ERC20C,
}, },
{ {
description: 'Buy an ERC721 then use it to pay maker fee (the other way).', leftMaker: AssetType.ERC1155FungibleA,
leftMaker: TokenType.ERC721A, rightMaker: AssetType.ERC1155FungibleA,
rightMaker: TokenType.ERC20A, leftMakerFee: AssetType.ERC20C,
leftMakerFee: TokenType.ERC20B, rightMakerFee: AssetType.ERC20C,
rightMakerFee: TokenType.ERC721A, leftTakerFee: AssetType.ERC20C,
leftTakerFee: TokenType.ERC20C, rightTakerFee: AssetType.ERC20C,
rightTakerFee: TokenType.ERC20C, },
{
leftMaker: AssetType.ERC1155NonFungibleLeftMaker,
rightMaker: AssetType.ERC20A,
leftMakerFee: AssetType.ERC20C,
rightMakerFee: AssetType.ERC20C,
leftTakerFee: AssetType.ERC20C,
rightTakerFee: AssetType.ERC20C,
},
{
leftMaker: AssetType.ERC20A,
rightMaker: AssetType.ERC1155NonFungibleRightMaker,
leftMakerFee: AssetType.ERC20C,
rightMakerFee: AssetType.ERC20C,
leftTakerFee: AssetType.ERC20C,
rightTakerFee: AssetType.ERC20C,
},
{
leftMaker: AssetType.ERC1155NonFungibleLeftMaker,
rightMaker: AssetType.ERC1155NonFungibleRightMaker,
leftMakerFee: AssetType.ERC20C,
rightMakerFee: AssetType.ERC20C,
leftTakerFee: AssetType.ERC20C,
rightTakerFee: AssetType.ERC20C,
},
{
leftMaker: AssetType.ERC1155FungibleA,
rightMaker: AssetType.ERC20A,
leftMakerFee: AssetType.ERC20C,
rightMakerFee: AssetType.ERC20C,
leftTakerFee: AssetType.ERC20C,
rightTakerFee: AssetType.ERC20C,
},
{
leftMaker: AssetType.ERC20A,
rightMaker: AssetType.ERC1155FungibleB,
leftMakerFee: AssetType.ERC20C,
rightMakerFee: AssetType.ERC20C,
leftTakerFee: AssetType.ERC20C,
rightTakerFee: AssetType.ERC20C,
},
{
leftMaker: AssetType.ERC1155FungibleB,
rightMaker: AssetType.ERC1155FungibleB,
leftMakerFee: AssetType.ERC20C,
rightMakerFee: AssetType.ERC20C,
leftTakerFee: AssetType.ERC20C,
rightTakerFee: AssetType.ERC20C,
},
{
leftMaker: AssetType.MultiAssetA,
rightMaker: AssetType.ERC20A,
leftMakerFee: AssetType.ERC20C,
rightMakerFee: AssetType.ERC20C,
leftTakerFee: AssetType.ERC20C,
rightTakerFee: AssetType.ERC20C,
},
{
leftMaker: AssetType.ERC20A,
rightMaker: AssetType.MultiAssetB,
leftMakerFee: AssetType.ERC20C,
rightMakerFee: AssetType.ERC20C,
leftTakerFee: AssetType.ERC20C,
rightTakerFee: AssetType.ERC20C,
},
{
leftMaker: AssetType.MultiAssetA,
rightMaker: AssetType.MultiAssetB,
leftMakerFee: AssetType.ERC20C,
rightMakerFee: AssetType.ERC20C,
leftTakerFee: AssetType.ERC20C,
rightTakerFee: AssetType.ERC20C,
},
{
leftMaker: AssetType.MultiAssetA,
rightMaker: AssetType.ERC1155FungibleA,
leftMakerFee: AssetType.ERC1155FungibleA,
rightMakerFee: AssetType.MultiAssetA,
leftTakerFee: AssetType.ERC20C,
rightTakerFee: AssetType.ERC20C,
},
{
description: 'Paying maker fees with the same ERC20 tokens being bought.',
leftMaker: AssetType.ERC20A,
rightMaker: AssetType.ERC20B,
leftMakerFee: AssetType.ERC20B,
rightMakerFee: AssetType.ERC20A,
leftTakerFee: AssetType.ERC20B,
rightTakerFee: AssetType.ERC20A,
},
{
description: 'Paying maker fees with the same ERC20 tokens being sold.',
leftMaker: AssetType.ERC20A,
rightMaker: AssetType.ERC20B,
leftMakerFee: AssetType.ERC20A,
rightMakerFee: AssetType.ERC20B,
leftTakerFee: AssetType.ERC20A,
rightTakerFee: AssetType.ERC20B,
},
{
description: 'Using all the same ERC20 asset.',
leftMaker: AssetType.ERC20A,
rightMaker: AssetType.ERC20A,
leftMakerFee: AssetType.ERC20A,
rightMakerFee: AssetType.ERC20A,
leftTakerFee: AssetType.ERC20A,
rightTakerFee: AssetType.ERC20A,
},
{
description: 'Paying fees with the same MAP assets being sold.',
leftMaker: AssetType.MultiAssetA,
rightMaker: AssetType.MultiAssetB,
leftMakerFee: AssetType.MultiAssetA,
rightMakerFee: AssetType.MultiAssetB,
leftTakerFee: AssetType.MultiAssetA,
rightTakerFee: AssetType.MultiAssetB,
},
{
description: 'Paying fees with the same MAP assets being bought.',
leftMaker: AssetType.MultiAssetA,
rightMaker: AssetType.MultiAssetB,
leftMakerFee: AssetType.MultiAssetB,
rightMakerFee: AssetType.MultiAssetA,
leftTakerFee: AssetType.MultiAssetB,
rightTakerFee: AssetType.MultiAssetA,
},
{
description: 'Using all the same MAP assets.',
leftMaker: AssetType.MultiAssetA,
rightMaker: AssetType.MultiAssetA,
leftMakerFee: AssetType.MultiAssetA,
rightMakerFee: AssetType.MultiAssetA,
leftTakerFee: AssetType.MultiAssetA,
rightTakerFee: AssetType.MultiAssetA,
}, },
{ {
description: 'Swapping ERC721s then using them to pay maker fees.', description: 'Swapping ERC721s then using them to pay maker fees.',
leftMaker: TokenType.ERC721A, leftMaker: AssetType.ERC721LeftMaker,
rightMaker: TokenType.ERC721B, rightMaker: AssetType.ERC721RightMaker,
leftMakerFee: TokenType.ERC721B, leftMakerFee: AssetType.ERC721RightMaker,
rightMakerFee: TokenType.ERC721A, rightMakerFee: AssetType.ERC721LeftMaker,
leftTakerFee: TokenType.ERC20A, leftTakerFee: AssetType.ERC20A,
rightTakerFee: TokenType.ERC20A, rightTakerFee: AssetType.ERC20A,
},
{
description: 'Swapping ERC1155 NFTs then using them to pay maker fees.',
leftMaker: AssetType.ERC1155NonFungibleLeftMaker,
rightMaker: AssetType.ERC1155NonFungibleRightMaker,
leftMakerFee: AssetType.ERC1155NonFungibleRightMaker,
rightMakerFee: AssetType.ERC1155NonFungibleLeftMaker,
leftTakerFee: AssetType.ERC20A,
rightTakerFee: AssetType.ERC20A,
}, },
{ {
description: 'Double-spend by trying to pay maker fees with sold ERC721 token (fail).', description: 'Double-spend by trying to pay maker fees with sold ERC721 token (fail).',
leftMaker: TokenType.ERC721A, leftMaker: AssetType.ERC721LeftMaker,
rightMaker: TokenType.ERC721B, rightMaker: AssetType.ERC721RightMaker,
leftMakerFee: TokenType.ERC721A, leftMakerFee: AssetType.ERC721LeftMaker,
rightMakerFee: TokenType.ERC721A, rightMakerFee: AssetType.ERC721LeftMaker,
leftTakerFee: TokenType.ERC20A, leftTakerFee: AssetType.ERC20A,
rightTakerFee: TokenType.ERC20A, rightTakerFee: AssetType.ERC20A,
shouldFail: true,
},
{
description: 'Double-spend by trying to pay maker fees with sold ERC1155 NFT (fail).',
leftMaker: AssetType.ERC20A,
rightMaker: AssetType.ERC1155NonFungibleLeftMaker,
leftMakerFee: AssetType.ERC20C,
rightMakerFee: AssetType.ERC1155NonFungibleLeftMaker,
leftTakerFee: AssetType.ERC20C,
rightTakerFee: AssetType.ERC20C,
shouldFail: true, shouldFail: true,
}, },
]; ];
let erc721TokenIdsByOwner: ERC721TokenIdsByOwner; let nameToERC20Asset: { [name: string]: string };
let nameToERC20Tokens: { [name: string]: string }; let nameToERC721Asset: { [name: string]: [string, BigNumber] };
let nameToERC721Tokens: { [name: string]: string }; let nameToERC1155FungibleAsset: { [name: string]: [string, BigNumber] };
let nameToERC1155NonFungibleAsset: { [name: string]: [string, BigNumber] };
let nameToMultiAssetAsset: { [name: string]: [BigNumber[], string[]] };
function getAssetData(tokenType: TokenType, ownerAddress: string): string { function getAssetData(assetType: AssetType): string {
const encodeERC20AssetData = assetDataUtils.encodeERC20AssetData; const encodeERC20AssetData = assetDataUtils.encodeERC20AssetData;
const encodeERC721AssetData = assetDataUtils.encodeERC721AssetData; const encodeERC721AssetData = assetDataUtils.encodeERC721AssetData;
if (nameToERC20Tokens[tokenType] !== undefined) { const encodeERC1155AssetData = assetDataUtils.encodeERC1155AssetData;
const tokenAddress = nameToERC20Tokens[tokenType]; const encodeMultiAssetData = assetDataUtils.encodeMultiAssetData;
if (nameToERC20Asset[assetType] !== undefined) {
const tokenAddress = nameToERC20Asset[assetType];
return encodeERC20AssetData(tokenAddress); return encodeERC20AssetData(tokenAddress);
} }
if (nameToERC721Tokens[tokenType] !== undefined) { if (nameToERC721Asset[assetType] !== undefined) {
const tokenAddress = nameToERC721Tokens[tokenType]; const [tokenAddress, tokenId] = nameToERC721Asset[assetType];
const tokenIdx = tokenType.charCodeAt(tokenType.length - 1) - 'A'.charCodeAt(0); return encodeERC721AssetData(tokenAddress, tokenId);
const tokenId = erc721TokenIdsByOwner[ownerAddress][tokenAddress][tokenIdx];
return encodeERC721AssetData(nameToERC721Tokens[tokenType], tokenId);
} }
return '0x'; if (nameToERC1155FungibleAsset[assetType] !== undefined) {
const [tokenAddress, tokenId] = nameToERC1155FungibleAsset[assetType];
return encodeERC1155AssetData(tokenAddress, [tokenId], [ONE], constants.NULL_BYTES);
}
if (nameToERC1155NonFungibleAsset[assetType] !== undefined) {
const [tokenAddress, tokenId] = nameToERC1155NonFungibleAsset[assetType];
return encodeERC1155AssetData(tokenAddress, [tokenId], [ONE], constants.NULL_BYTES);
}
if (nameToMultiAssetAsset[assetType] !== undefined) {
const [amounts, nestedAssetData] = nameToMultiAssetAsset[assetType];
return encodeMultiAssetData(amounts, nestedAssetData);
}
throw new Error(`Unknown asset type: ${assetType}`);
} }
before(async () => { before(async () => {
erc721TokenIdsByOwner = await erc721Wrapper.getBalancesAsync(); nameToERC20Asset = {
nameToERC20Tokens = { ERC20_A: erc20Tokens[0].address,
ERC20A: erc20Tokens[0].address, ERC20_B: erc20Tokens[1].address,
ERC20B: erc20Tokens[1].address, ERC20_C: erc20Tokens[2].address,
ERC20C: erc20Tokens[0].address, ERC20_D: erc20Tokens[3].address,
ERC20D: erc20Tokens[1].address,
}; };
nameToERC721Tokens = { const erc721TokenIds = _.mapValues(tokenBalances.erc721, v => v[defaultERC721AssetAddress.address][0]);
ERC721A: erc721Token.address, nameToERC721Asset = {
ERC721B: erc721Token.address, ERC721_LEFT_MAKER: [defaultERC721AssetAddress.address, erc721TokenIds[makerAddressLeft]],
ERC721C: erc721Token.address, ERC721_RIGHT_MAKER: [defaultERC721AssetAddress.address, erc721TokenIds[makerAddressRight]],
ERC721D: erc721Token.address, ERC721_TAKER: [defaultERC721AssetAddress.address, erc721TokenIds[takerAddress]],
};
const erc1155FungibleTokens = _.keys(
_.values(tokenBalances.erc1155)[0][defaultERC1155AssetAddress].fungible,
).map(k => new BigNumber(k));
nameToERC1155FungibleAsset = {
ERC1155_FUNGIBLE_A: [defaultERC1155AssetAddress, erc1155FungibleTokens[0]],
ERC1155_FUNGIBLE_B: [defaultERC1155AssetAddress, erc1155FungibleTokens[1]],
ERC1155_FUNGIBLE_C: [defaultERC1155AssetAddress, erc1155FungibleTokens[2]],
ERC1155_FUNGIBLE_D: [defaultERC1155AssetAddress, erc1155FungibleTokens[3]],
};
const erc1155NonFungibleTokenIds = _.mapValues(
tokenBalances.erc1155,
v => v[defaultERC1155AssetAddress].nonFungible[0],
);
nameToERC1155NonFungibleAsset = {
ERC1155_NON_FUNGIBLE_LEFT_MAKER: [
defaultERC1155AssetAddress,
erc1155NonFungibleTokenIds[makerAddressLeft],
],
ERC1155_NON_FUNGIBLE_RIGHT_MAKER: [
defaultERC1155AssetAddress,
erc1155NonFungibleTokenIds[makerAddressRight],
],
ERC1155_NON_FUNGIBLE_TAKER: [defaultERC1155AssetAddress, erc1155NonFungibleTokenIds[takerAddress]],
};
nameToMultiAssetAsset = {
MULTI_ASSET_A: [
[ONE, TWO],
[
assetDataUtils.encodeERC20AssetData(erc20Tokens[0].address),
assetDataUtils.encodeERC1155AssetData(
defaultERC1155AssetAddress,
[erc1155FungibleTokens[0]],
[ONE],
constants.NULL_BYTES,
),
],
],
MULTI_ASSET_B: [
[ONE, TWO],
[
assetDataUtils.encodeERC20AssetData(erc20Tokens[1].address),
assetDataUtils.encodeERC1155AssetData(
defaultERC1155AssetAddress,
[erc1155FungibleTokens[1]],
[ONE],
constants.NULL_BYTES,
),
],
],
MULTI_ASSET_C: [
[ONE, TWO],
[
assetDataUtils.encodeERC20AssetData(erc20Tokens[2].address),
assetDataUtils.encodeERC1155AssetData(
defaultERC1155AssetAddress,
[erc1155FungibleTokens[2]],
[ONE],
constants.NULL_BYTES,
),
],
],
MULTI_ASSET_D: [
[ONE, TWO],
[
assetDataUtils.encodeERC20AssetData(erc20Tokens[3].address),
assetDataUtils.encodeERC1155AssetData(
erc1155Token.address,
[erc1155FungibleTokens[3]],
[ONE],
constants.NULL_BYTES,
),
],
],
}; };
}); });
for (const combo of feeAssetCombinations) { for (const combo of assetCombinations) {
const description = combo.description || JSON.stringify(combo); const description = combo.description || JSON.stringify(combo);
it(description, async () => { it(description, async () => {
// Create orders to match. For ERC20s, there will be a spread. // Create orders to match. For ERC20s, there will be a spread.
const leftMakerAssetAmount = combo.leftMaker.startsWith('ERC20') const leftMakerAssetAmount = _.includes(fungibleTypes, combo.leftMaker)
? Web3Wrapper.toBaseUnitAmount(15, 18) ? Web3Wrapper.toBaseUnitAmount(15, 18)
: Web3Wrapper.toBaseUnitAmount(1, 0); : Web3Wrapper.toBaseUnitAmount(1, 0);
const leftTakerAssetAmount = combo.rightMaker.startsWith('ERC20') const leftTakerAssetAmount = _.includes(fungibleTypes, combo.rightMaker)
? Web3Wrapper.toBaseUnitAmount(30, 18) ? Web3Wrapper.toBaseUnitAmount(30, 18)
: Web3Wrapper.toBaseUnitAmount(1, 0); : Web3Wrapper.toBaseUnitAmount(1, 0);
const rightMakerAssetAmount = combo.rightMaker.startsWith('ERC20') const rightMakerAssetAmount = _.includes(fungibleTypes, combo.rightMaker)
? Web3Wrapper.toBaseUnitAmount(30, 18) ? Web3Wrapper.toBaseUnitAmount(30, 18)
: Web3Wrapper.toBaseUnitAmount(1, 0); : Web3Wrapper.toBaseUnitAmount(1, 0);
const rightTakerAssetAmount = combo.leftMaker.startsWith('ERC20') const rightTakerAssetAmount = _.includes(fungibleTypes, combo.leftMaker)
? Web3Wrapper.toBaseUnitAmount(14, 18) ? Web3Wrapper.toBaseUnitAmount(14, 18)
: Web3Wrapper.toBaseUnitAmount(1, 0); : Web3Wrapper.toBaseUnitAmount(1, 0);
const leftMakerFeeAssetAmount = combo.leftMakerFee.startsWith('ERC20') const leftMakerFeeAssetAmount = _.includes(fungibleTypes, combo.leftMakerFee)
? Web3Wrapper.toBaseUnitAmount(8, 12) ? Web3Wrapper.toBaseUnitAmount(8, 12)
: Web3Wrapper.toBaseUnitAmount(1, 0); : Web3Wrapper.toBaseUnitAmount(1, 0);
const rightMakerFeeAssetAmount = combo.rightMakerFee.startsWith('ERC20') const rightMakerFeeAssetAmount = _.includes(fungibleTypes, combo.rightMakerFee)
? Web3Wrapper.toBaseUnitAmount(7, 12) ? Web3Wrapper.toBaseUnitAmount(7, 12)
: Web3Wrapper.toBaseUnitAmount(1, 0); : Web3Wrapper.toBaseUnitAmount(1, 0);
const leftTakerFeeAssetAmount = combo.leftTakerFee.startsWith('ERC20') const leftTakerFeeAssetAmount = _.includes(fungibleTypes, combo.leftTakerFee)
? Web3Wrapper.toBaseUnitAmount(6, 12) ? Web3Wrapper.toBaseUnitAmount(6, 12)
: Web3Wrapper.toBaseUnitAmount(1, 0); : Web3Wrapper.toBaseUnitAmount(1, 0);
const rightTakerFeeAssetAmount = combo.rightTakerFee.startsWith('ERC20') const rightTakerFeeAssetAmount = _.includes(fungibleTypes, combo.rightTakerFee)
? Web3Wrapper.toBaseUnitAmount(5, 12) ? Web3Wrapper.toBaseUnitAmount(5, 12)
: Web3Wrapper.toBaseUnitAmount(1, 0); : Web3Wrapper.toBaseUnitAmount(1, 0);
const leftMakerAssetReceivedByTakerAmount = combo.leftMaker.startsWith('ERC20') const leftMakerAssetReceivedByTakerAmount = _.includes(fungibleTypes, combo.leftMaker)
? leftMakerAssetAmount.minus(rightTakerAssetAmount) ? leftMakerAssetAmount.minus(rightTakerAssetAmount)
: Web3Wrapper.toBaseUnitAmount(0, 0); : Web3Wrapper.toBaseUnitAmount(0, 0);
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
makerAssetData: getAssetData(combo.leftMaker, makerAddressLeft), makerAssetData: getAssetData(combo.leftMaker),
takerAssetData: getAssetData(combo.rightMaker, makerAddressRight), takerAssetData: getAssetData(combo.rightMaker),
makerFeeAssetData: getAssetData(combo.leftMakerFee, makerAddressLeft), makerFeeAssetData: getAssetData(combo.leftMakerFee),
takerFeeAssetData: getAssetData(combo.leftTakerFee, takerAddress), takerFeeAssetData: getAssetData(combo.leftTakerFee),
makerAssetAmount: leftMakerAssetAmount, makerAssetAmount: leftMakerAssetAmount,
takerAssetAmount: leftTakerAssetAmount, takerAssetAmount: leftTakerAssetAmount,
makerFee: leftMakerFeeAssetAmount, makerFee: leftMakerFeeAssetAmount,
takerFee: leftTakerFeeAssetAmount, takerFee: leftTakerFeeAssetAmount,
}); });
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
makerAssetData: getAssetData(combo.rightMaker, makerAddressRight), makerAssetData: getAssetData(combo.rightMaker),
takerAssetData: getAssetData(combo.leftMaker, makerAddressLeft), takerAssetData: getAssetData(combo.leftMaker),
makerFeeAssetData: getAssetData(combo.rightMakerFee, makerAddressRight), makerFeeAssetData: getAssetData(combo.rightMakerFee),
takerFeeAssetData: getAssetData(combo.rightTakerFee, takerAddress), takerFeeAssetData: getAssetData(combo.rightTakerFee),
makerAssetAmount: rightMakerAssetAmount, makerAssetAmount: rightMakerAssetAmount,
takerAssetAmount: rightTakerAssetAmount, takerAssetAmount: rightTakerAssetAmount,
makerFee: rightMakerFeeAssetAmount, makerFee: rightMakerFeeAssetAmount,
@ -1536,15 +1712,15 @@ describe('matchOrders', () => {
const expectedTransferAmounts = { const expectedTransferAmounts = {
// Left Maker // Left Maker
leftMakerAssetSoldByLeftMakerAmount: leftMakerAssetAmount, leftMakerAssetSoldByLeftMakerAmount: leftMakerAssetAmount,
leftMakerFeeAssetPaidByLeftMakerAmount: leftMakerFeeAssetAmount, // 100% leftMakerFeeAssetPaidByLeftMakerAmount: leftMakerFeeAssetAmount,
// Right Maker // Right Maker
rightMakerAssetSoldByRightMakerAmount: rightMakerAssetAmount, rightMakerAssetSoldByRightMakerAmount: rightMakerAssetAmount,
leftMakerAssetBoughtByRightMakerAmount: rightTakerAssetAmount, leftMakerAssetBoughtByRightMakerAmount: rightTakerAssetAmount,
rightMakerFeeAssetPaidByRightMakerAmount: rightMakerFeeAssetAmount, // 100% rightMakerFeeAssetPaidByRightMakerAmount: rightMakerFeeAssetAmount,
// Taker // Taker
leftMakerAssetReceivedByTakerAmount, leftMakerAssetReceivedByTakerAmount,
leftTakerFeeAssetPaidByTakerAmount: leftTakerFeeAssetAmount, // 100% leftTakerFeeAssetPaidByTakerAmount: leftTakerFeeAssetAmount,
rightTakerFeeAssetPaidByTakerAmount: rightTakerFeeAssetAmount, // 100% rightTakerFeeAssetPaidByTakerAmount: rightTakerFeeAssetAmount,
}; };
if (!combo.shouldFail) { if (!combo.shouldFail) {
await matchOrderTester.matchOrdersAndAssertEffectsAsync( await matchOrderTester.matchOrdersAndAssertEffectsAsync(
@ -1564,18 +1740,4 @@ describe('matchOrders', () => {
}); });
}); });
}); });
function getIndividualERC1155Holdings(
erc1155HoldingsByOwner: ERC1155HoldingsByOwner,
tokenAddress: string,
ownerAddress: string,
): IndividualERC1155Holdings {
return {
fungible: erc1155HoldingsByOwner.fungible[ownerAddress][tokenAddress],
nonFungible: _.uniqBy(_.flatten(_.values(
erc1155HoldingsByOwner.nonFungible[ownerAddress][tokenAddress])),
v => v.toString(10),
),
};
}
// tslint:disable-line:max-file-line-count // tslint:disable-line:max-file-line-count

View File

@ -96,24 +96,24 @@ export class AssetWrapper {
assetProxyData.tokenAddress, assetProxyData.tokenAddress,
assetProxyData.tokenId, assetProxyData.tokenId,
); );
if (!doesTokenExist && desiredBalance.gt(0)) { if (!doesTokenExist && desiredBalance.gte(1)) {
await erc721Wrapper.mintAsync(assetProxyData.tokenAddress, assetProxyData.tokenId, userAddress); await erc721Wrapper.mintAsync(assetProxyData.tokenAddress, assetProxyData.tokenId, userAddress);
return; return;
} else if (!doesTokenExist && desiredBalance.lte(0)) { } else if (!doesTokenExist && desiredBalance.lt(1)) {
return; // noop return; // noop
} }
const tokenOwner = await erc721Wrapper.ownerOfAsync( const tokenOwner = await erc721Wrapper.ownerOfAsync(
assetProxyData.tokenAddress, assetProxyData.tokenAddress,
assetProxyData.tokenId, assetProxyData.tokenId,
); );
if (userAddress !== tokenOwner && desiredBalance.gt(0)) { if (userAddress !== tokenOwner && desiredBalance.gte(1)) {
await erc721Wrapper.transferFromAsync( await erc721Wrapper.transferFromAsync(
assetProxyData.tokenAddress, assetProxyData.tokenAddress,
assetProxyData.tokenId, assetProxyData.tokenId,
tokenOwner, tokenOwner,
userAddress, userAddress,
); );
} else if (tokenOwner === userAddress && desiredBalance.lte(0)) { } else if (tokenOwner === userAddress && desiredBalance.lt(1)) {
// Burn token // Burn token
await erc721Wrapper.transferFromAsync( await erc721Wrapper.transferFromAsync(
assetProxyData.tokenAddress, assetProxyData.tokenAddress,
@ -123,8 +123,8 @@ export class AssetWrapper {
); );
return; return;
} else if ( } else if (
(userAddress !== tokenOwner && desiredBalance.lte(0)) || (userAddress !== tokenOwner && desiredBalance.lt(1)) ||
(tokenOwner === userAddress && desiredBalance.gt(0)) (tokenOwner === userAddress && desiredBalance.gte(1))
) { ) {
return; // noop return; // noop
} }
@ -168,7 +168,7 @@ export class AssetWrapper {
} }
} else { } else {
const nftOwner = await assetWrapper.getOwnerOfAsync(tokenId); const nftOwner = await assetWrapper.getOwnerOfAsync(tokenId);
if (scaledDesiredBalance.gt(0)) { if (scaledDesiredBalance.gte(1)) {
if (nftOwner === userAddress) { if (nftOwner === userAddress) {
// Nothing to do. // Nothing to do.
} else if (nftOwner !== constants.NULL_ADDRESS) { } else if (nftOwner !== constants.NULL_ADDRESS) {

View File

@ -254,7 +254,8 @@ export class FillOrderCombinatorialUtils {
// ExpirationTimeSecondsScenario.InPast, // ExpirationTimeSecondsScenario.InPast,
]; ];
const makerAssetDataScenario = [ const makerAssetDataScenario = [
AssetDataScenario.ERC20FiveDecimals, // FeeAssetDataScenario.ERC20ZeroDecimals,
// AssetDataScenario.ERC20FiveDecimals,
AssetDataScenario.ERC20EighteenDecimals, AssetDataScenario.ERC20EighteenDecimals,
AssetDataScenario.ERC721, AssetDataScenario.ERC721,
AssetDataScenario.ERC1155Fungible, AssetDataScenario.ERC1155Fungible,
@ -262,7 +263,8 @@ export class FillOrderCombinatorialUtils {
AssetDataScenario.MultiAssetERC20, AssetDataScenario.MultiAssetERC20,
]; ];
const takerAssetDataScenario = [ const takerAssetDataScenario = [
AssetDataScenario.ERC20FiveDecimals, // FeeAssetDataScenario.ERC20ZeroDecimals,
// AssetDataScenario.ERC20FiveDecimals,
AssetDataScenario.ERC20EighteenDecimals, AssetDataScenario.ERC20EighteenDecimals,
AssetDataScenario.ERC721, AssetDataScenario.ERC721,
AssetDataScenario.ERC1155Fungible, AssetDataScenario.ERC1155Fungible,
@ -270,7 +272,8 @@ export class FillOrderCombinatorialUtils {
AssetDataScenario.MultiAssetERC20, AssetDataScenario.MultiAssetERC20,
]; ];
const makerFeeAssetDataScenario = [ const makerFeeAssetDataScenario = [
FeeAssetDataScenario.ERC20FiveDecimals, // FeeAssetDataScenario.ERC20ZeroDecimals,
// FeeAssetDataScenario.ERC20FiveDecimals,
FeeAssetDataScenario.ERC20EighteenDecimals, FeeAssetDataScenario.ERC20EighteenDecimals,
FeeAssetDataScenario.ERC721, FeeAssetDataScenario.ERC721,
FeeAssetDataScenario.ERC1155Fungible, FeeAssetDataScenario.ERC1155Fungible,
@ -280,7 +283,8 @@ export class FillOrderCombinatorialUtils {
FeeAssetDataScenario.TakerToken, FeeAssetDataScenario.TakerToken,
]; ];
const takerFeeAssetDataScenario = [ const takerFeeAssetDataScenario = [
FeeAssetDataScenario.ERC20FiveDecimals, // FeeAssetDataScenario.ERC20ZeroDecimals,
// FeeAssetDataScenario.ERC20FiveDecimals,
FeeAssetDataScenario.ERC20EighteenDecimals, FeeAssetDataScenario.ERC20EighteenDecimals,
FeeAssetDataScenario.ERC721, FeeAssetDataScenario.ERC721,
FeeAssetDataScenario.ERC1155Fungible, FeeAssetDataScenario.ERC1155Fungible,

View File

@ -1,5 +1,4 @@
export * from './exchange_wrapper'; export * from './exchange_wrapper';
export * from './match_order_tester';
export * from './exchange_data_encoder'; export * from './exchange_data_encoder';
export * from './types'; export * from './types';
export * from './constants'; export * from './constants';

View File

@ -1,5 +1,5 @@
import { ERC20Wrapper, ERC721Wrapper } from '@0x/contracts-asset-proxy'; import { ERC1155ProxyWrapper, ERC20Wrapper, ERC721Wrapper } from '@0x/contracts-asset-proxy';
import { chaiSetup, OrderStatus, TokenBalancesByOwner } from '@0x/contracts-test-utils'; import { chaiSetup, ERC1155HoldingsByOwner, OrderStatus } from '@0x/contracts-test-utils';
import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
import { AssetProxyId, SignedOrder } from '@0x/types'; import { AssetProxyId, SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
@ -14,6 +14,13 @@ const ZERO = new BigNumber(0);
chaiSetup.configure(); chaiSetup.configure();
const expect = chai.expect; const expect = chai.expect;
export interface IndividualERC1155Holdings {
fungible: {
[tokenId: string]: BigNumber;
};
nonFungible: BigNumber[];
}
export interface FillEventArgs { export interface FillEventArgs {
orderHash: string; orderHash: string;
makerAddress: string; makerAddress: string;
@ -46,7 +53,32 @@ export interface MatchTransferAmounts {
export interface MatchResults { export interface MatchResults {
orders: MatchedOrders; orders: MatchedOrders;
fills: FillEventArgs[]; fills: FillEventArgs[];
balances: TokenBalancesByOwner; balances: TokenBalances;
}
export interface ERC1155Holdings {
[owner: string]: {
[contract: string]: {
fungible: {
[tokenId: string]: BigNumber;
};
nonFungible: BigNumber[];
};
};
}
export interface TokenBalances {
erc20: {
[owner: string]: {
[contract: string]: BigNumber;
};
};
erc721: {
[owner: string]: {
[contract: string]: BigNumber[];
};
};
erc1155: ERC1155Holdings;
} }
export interface MatchedOrders { export interface MatchedOrders {
@ -66,14 +98,16 @@ export class MatchOrderTester {
public exchangeWrapper: ExchangeWrapper; public exchangeWrapper: ExchangeWrapper;
public erc20Wrapper: ERC20Wrapper; public erc20Wrapper: ERC20Wrapper;
public erc721Wrapper: ERC721Wrapper; public erc721Wrapper: ERC721Wrapper;
public erc1155ProxyWrapper: ERC1155ProxyWrapper;
public matchOrdersCallAsync?: MatchOrdersAsyncCall; public matchOrdersCallAsync?: MatchOrdersAsyncCall;
private readonly _initialTokenBalancesPromise: Promise<TokenBalancesByOwner>; private readonly _initialTokenBalancesPromise: Promise<TokenBalances>;
/** /**
* Constructs new MatchOrderTester. * Constructs new MatchOrderTester.
* @param exchangeWrapper Used to call to the Exchange. * @param exchangeWrapper Used to call to the Exchange.
* @param erc20Wrapper Used to fetch ERC20 balances. * @param erc20Wrapper Used to fetch ERC20 balances.
* @param erc721Wrapper Used to fetch ERC721 token owners. * @param erc721Wrapper Used to fetch ERC721 token owners.
* @param erc1155Wrapper Used to fetch ERC1155 token owners.
* @param matchOrdersCallAsync Optional, custom caller for * @param matchOrdersCallAsync Optional, custom caller for
* `ExchangeWrapper.matchOrdersAsync()`. * `ExchangeWrapper.matchOrdersAsync()`.
*/ */
@ -81,11 +115,13 @@ export class MatchOrderTester {
exchangeWrapper: ExchangeWrapper, exchangeWrapper: ExchangeWrapper,
erc20Wrapper: ERC20Wrapper, erc20Wrapper: ERC20Wrapper,
erc721Wrapper: ERC721Wrapper, erc721Wrapper: ERC721Wrapper,
erc1155ProxyWrapper: ERC1155ProxyWrapper,
matchOrdersCallAsync?: MatchOrdersAsyncCall, matchOrdersCallAsync?: MatchOrdersAsyncCall,
) { ) {
this.exchangeWrapper = exchangeWrapper; this.exchangeWrapper = exchangeWrapper;
this.erc20Wrapper = erc20Wrapper; this.erc20Wrapper = erc20Wrapper;
this.erc721Wrapper = erc721Wrapper; this.erc721Wrapper = erc721Wrapper;
this.erc1155ProxyWrapper = erc1155ProxyWrapper;
this.matchOrdersCallAsync = matchOrdersCallAsync; this.matchOrdersCallAsync = matchOrdersCallAsync;
this._initialTokenBalancesPromise = this.getBalancesAsync(); this._initialTokenBalancesPromise = this.getBalancesAsync();
} }
@ -103,7 +139,7 @@ export class MatchOrderTester {
orders: MatchedOrders, orders: MatchedOrders,
takerAddress: string, takerAddress: string,
expectedTransferAmounts: Partial<MatchTransferAmounts>, expectedTransferAmounts: Partial<MatchTransferAmounts>,
initialTokenBalances?: TokenBalancesByOwner, initialTokenBalances?: TokenBalances,
): Promise<MatchResults> { ): Promise<MatchResults> {
await assertInitialOrderStatesAsync(orders, this.exchangeWrapper); await assertInitialOrderStatesAsync(orders, this.exchangeWrapper);
// Get the token balances before executing `matchOrders()`. // Get the token balances before executing `matchOrders()`.
@ -136,8 +172,8 @@ export class MatchOrderTester {
/** /**
* Fetch the current token balances of all known accounts. * Fetch the current token balances of all known accounts.
*/ */
public async getBalancesAsync(): Promise<TokenBalancesByOwner> { public async getBalancesAsync(): Promise<TokenBalances> {
return getTokenBalancesAsync(this.erc20Wrapper, this.erc721Wrapper); return getTokenBalancesAsync(this.erc20Wrapper, this.erc721Wrapper, this.erc1155ProxyWrapper);
} }
private async _executeMatchOrdersAsync( private async _executeMatchOrdersAsync(
@ -203,7 +239,7 @@ function toFullMatchTransferAmounts(partial: Partial<MatchTransferAmounts>): Mat
function simulateMatchOrders( function simulateMatchOrders(
orders: MatchedOrders, orders: MatchedOrders,
takerAddress: string, takerAddress: string,
tokenBalances: TokenBalancesByOwner, tokenBalances: TokenBalances,
transferAmounts: MatchTransferAmounts, transferAmounts: MatchTransferAmounts,
): MatchResults { ): MatchResults {
// prettier-ignore // prettier-ignore
@ -336,8 +372,47 @@ function transferAsset(
const tokenId = erc721AssetData.tokenId; const tokenId = erc721AssetData.tokenId;
const fromTokens = matchResults.balances.erc721[fromAddress][assetAddress]; const fromTokens = matchResults.balances.erc721[fromAddress][assetAddress];
const toTokens = matchResults.balances.erc721[toAddress][assetAddress]; const toTokens = matchResults.balances.erc721[toAddress][assetAddress];
_.remove(fromTokens, tokenId); if (amount.gte(1)) {
const tokenIndex = _.findIndex(fromTokens, t => t.eq(tokenId));
if (tokenIndex !== -1) {
fromTokens.splice(tokenIndex, 1);
toTokens.push(tokenId); toTokens.push(tokenId);
}
}
break;
}
case AssetProxyId.ERC1155: {
const erc1155AssetData = assetDataUtils.decodeERC1155AssetData(assetData);
const assetAddress = erc1155AssetData.tokenAddress;
const fromBalances = matchResults.balances.erc1155[fromAddress][assetAddress];
const toBalances = matchResults.balances.erc1155[toAddress][assetAddress];
for (const i of _.times(erc1155AssetData.tokenIds.length)) {
const tokenId = erc1155AssetData.tokenIds[i];
const tokenValue = erc1155AssetData.tokenValues[i];
const tokenAmount = amount.times(tokenValue);
if (tokenAmount.gt(0)) {
const tokenIndex = _.findIndex(fromBalances.nonFungible, t => t.eq(tokenId));
if (tokenIndex !== -1) {
// Transfer a non-fungible.
fromBalances.nonFungible.splice(tokenIndex, 1);
toBalances.nonFungible.push(tokenId);
} else {
// Transfer a fungible.
const _tokenId = tokenId.toString(10);
fromBalances.fungible[_tokenId] = fromBalances.fungible[_tokenId].minus(tokenAmount);
toBalances.fungible[_tokenId] = toBalances.fungible[_tokenId].plus(tokenAmount);
}
}
}
break;
}
case AssetProxyId.MultiAsset: {
const multiAssetData = assetDataUtils.decodeMultiAssetData(assetData);
for (const i of _.times(multiAssetData.amounts.length)) {
const nestedAmount = amount.times(multiAssetData.amounts[i]);
const nestedAssetData = multiAssetData.nestedAssetData[i];
transferAsset(fromAddress, toAddress, nestedAmount, nestedAssetData, matchResults);
}
break; break;
} }
default: default:
@ -355,7 +430,7 @@ function transferAsset(
async function assertMatchResultsAsync( async function assertMatchResultsAsync(
matchResults: MatchResults, matchResults: MatchResults,
transactionReceipt: TransactionReceiptWithDecodedLogs, transactionReceipt: TransactionReceiptWithDecodedLogs,
actualTokenBalances: TokenBalancesByOwner, actualTokenBalances: TokenBalances,
exchangeWrapper: ExchangeWrapper, exchangeWrapper: ExchangeWrapper,
): Promise<void> { ): Promise<void> {
// Check the fill events. // Check the fill events.
@ -462,21 +537,8 @@ function extractFillEventsfromReceipt(receipt: TransactionReceiptWithDecodedLogs
* @param expectedBalances Expected balances. * @param expectedBalances Expected balances.
* @param actualBalances Actual balances. * @param actualBalances Actual balances.
*/ */
function assertBalances(expectedBalances: TokenBalancesByOwner, actualBalances: TokenBalancesByOwner): void { function assertBalances(expectedBalances: TokenBalances, actualBalances: TokenBalances): void {
// ERC20 Balances expect(encodeTokenBalances(expectedBalances)).to.deep.equal(encodeTokenBalances(actualBalances));
expect(actualBalances.erc20, 'ERC20 balances').to.deep.equal(expectedBalances.erc20);
// ERC721 Token Ids
const sortedExpectedERC721Balances = _.mapValues(expectedBalances.erc721, tokenIdsByOwner => {
_.mapValues(tokenIdsByOwner, tokenIds => {
_.sortBy(tokenIds);
});
});
const sortedActualERC721Balances = _.mapValues(actualBalances.erc721, tokenIdsByOwner => {
_.mapValues(tokenIdsByOwner, tokenIds => {
_.sortBy(tokenIds);
});
});
expect(sortedExpectedERC721Balances, 'ERC721 balances').to.deep.equal(sortedActualERC721Balances);
} }
/** /**
@ -533,17 +595,57 @@ async function assertPostExchangeStateAsync(
* Retrive the current token balances of all known addresses. * Retrive the current token balances of all known addresses.
* @param erc20Wrapper The ERC20Wrapper instance. * @param erc20Wrapper The ERC20Wrapper instance.
* @param erc721Wrapper The ERC721Wrapper instance. * @param erc721Wrapper The ERC721Wrapper instance.
* @return A promise that resolves to a `TokenBalancesByOwner`. * @param erc1155Wrapper The ERC1155ProxyWrapper instance.
* @return A promise that resolves to a `TokenBalances`.
*/ */
export async function getTokenBalancesAsync( export async function getTokenBalancesAsync(
erc20Wrapper: ERC20Wrapper, erc20Wrapper: ERC20Wrapper,
erc721Wrapper: ERC721Wrapper, erc721Wrapper: ERC721Wrapper,
): Promise<TokenBalancesByOwner> { erc1155ProxyWrapper: ERC1155ProxyWrapper,
const [erc20, erc721] = await Promise.all([erc20Wrapper.getBalancesAsync(), erc721Wrapper.getBalancesAsync()]); ): Promise<TokenBalances> {
const [erc20, erc721, erc1155] = await Promise.all([
erc20Wrapper.getBalancesAsync(),
erc721Wrapper.getBalancesAsync(),
erc1155ProxyWrapper.getBalancesAsync(),
]);
return { return {
erc20, erc20,
erc721, erc721,
erc1155: transformERC1155Holdings(erc1155),
}; };
} }
/**
* Restructures `ERC1155HoldingsByOwner` to be compatible with `TokenBalances.erc1155`.
* @param erc1155HoldingsByOwner Holdings returned by `ERC1155ProxyWrapper.getBalancesAsync()`.
*/
function transformERC1155Holdings(erc1155HoldingsByOwner: ERC1155HoldingsByOwner): ERC1155Holdings {
const result = {};
for (const owner of _.keys(erc1155HoldingsByOwner.fungible)) {
for (const contract of _.keys(erc1155HoldingsByOwner.fungible[owner])) {
_.set(result as any, [owner, contract, 'fungible'], erc1155HoldingsByOwner.fungible[owner][contract]);
}
}
for (const owner of _.keys(erc1155HoldingsByOwner.nonFungible)) {
for (const contract of _.keys(erc1155HoldingsByOwner.nonFungible[owner])) {
const tokenIds = _.flatten(_.values(erc1155HoldingsByOwner.nonFungible[owner][contract]));
_.set(result as any, [owner, contract, 'nonFungible'], _.uniqBy(tokenIds, v => v.toString(10)));
}
}
return result;
}
function encodeTokenBalances(obj: any): any {
if (!_.isPlainObject(obj)) {
if (BigNumber.isBigNumber(obj)) {
return obj.toString(10);
}
if (_.isArray(obj)) {
return _.sortBy(obj, v => encodeTokenBalances(v));
}
return obj;
}
const keys = _.keys(obj).sort();
return _.zip(keys, keys.map(k => encodeTokenBalances(obj[k])));
}
// tslint:disable-line:max-file-line-count // tslint:disable-line:max-file-line-count