@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",
"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', () => {
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 () => {
const fillScenario = {
...defaultFillScenario,
@ -573,14 +591,14 @@ describe('FillOrder Tests', () => {
AssetDataScenario.ERC1155NonFungible,
AssetDataScenario.MultiAssetERC20,
];
for (const [makerAssetData, takerAssetData] of getAllPossiblePairs(assetDataScenarios)) {
it(`should successfully exchange ${makerAssetData} for ${takerAssetData}`, async () => {
for (const [makerAsset, takerAsset] of getAllPossiblePairs(assetDataScenarios)) {
it(`should successfully exchange ${makerAsset} for ${takerAsset}`, async () => {
const fillScenario = {
...defaultFillScenario,
orderScenario: {
...defaultFillScenario.orderScenario,
makerAssetDataScenario: makerAssetData,
takerAssetDataScenario: takerAssetData,
makerAssetDataScenario: makerAsset,
takerAssetDataScenario: takerAsset,
},
takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount,
};
@ -590,21 +608,21 @@ describe('FillOrder Tests', () => {
});
describe('Maker/taker fee asset combinations', () => {
const feeAssetDataScenarios = [
const feeAssetPairs = getAllPossiblePairs([
FeeAssetDataScenario.ERC20EighteenDecimals,
FeeAssetDataScenario.ERC721,
FeeAssetDataScenario.ERC1155Fungible,
FeeAssetDataScenario.ERC1155NonFungible,
FeeAssetDataScenario.MultiAssetERC20,
];
for (const [makerFeeAssetData, takerFeeAssetData] of getAllPossiblePairs(feeAssetDataScenarios)) {
it(`should successfully pay maker fee ${makerFeeAssetData} and taker fee ${takerFeeAssetData}`, async () => {
]);
for (const [makerFeeAsset, takerFeeAsset] of feeAssetPairs) {
it(`should successfully pay maker fee ${makerFeeAsset} and taker fee ${takerFeeAsset}`, async () => {
const fillScenario = {
...defaultFillScenario,
orderScenario: {
...defaultFillScenario.orderScenario,
makerFeeAssetDataScenario: makerFeeAssetData,
takerFeeAssetDataScenario: takerFeeAssetData,
makerFeeAssetDataScenario: makerFeeAsset,
takerFeeAssetDataScenario: takerFeeAsset,
},
takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount,
};

View File

@ -14,8 +14,6 @@ import { DummyERC721TokenContract } from '@0x/contracts-erc721';
import {
chaiSetup,
constants,
ERC1155HoldingsByOwner,
ERC721TokenIdsByOwner,
OrderFactory,
provider,
txDefaults,
@ -34,17 +32,14 @@ import {
constants as exchangeConstants,
ExchangeContract,
ExchangeWrapper,
MatchOrderTester,
ReentrantERC20TokenContract,
TestExchangeInternalsContract,
} from '../src';
interface IndividualERC1155Holdings {
fungible: {
[tokenId: string]: BigNumber;
};
nonFungible: BigNumber[];
}
import { MatchOrderTester, TokenBalances } from './utils/match_order_tester';
const ONE = new BigNumber(1);
const TWO = new BigNumber(2);
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
chaiSetup.configure();
@ -76,14 +71,12 @@ describe('matchOrders', () => {
let orderFactoryLeft: OrderFactory;
let orderFactoryRight: OrderFactory;
let erc721LeftMakerAssetIds: BigNumber[];
let erc721RightMakerAssetIds: BigNumber[];
let erc1155LeftMakerHoldings: IndividualERC1155Holdings;
let erc1155RightMakerHoldings: IndividualERC1155Holdings;
let tokenBalances: TokenBalances;
let defaultERC20MakerAssetAddress: string;
let defaultERC20TakerAssetAddress: string;
let defaultERC721AssetAddress: string;
let defaultERC1155AssetAddress: string;
let defaultFeeTokenAddress: string;
let matchOrderTester: MatchOrderTester;
@ -109,41 +102,25 @@ describe('matchOrders', () => {
feeRecipientAddressLeft,
feeRecipientAddressRight,
] = accounts);
const addressesWithBalances = usedAddresses.slice(1);
// Create wrappers
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
erc1155ProxyWrapper = new ERC1155ProxyWrapper(provider, usedAddresses, owner);
erc20Wrapper = new ERC20Wrapper(provider, addressesWithBalances, owner);
erc721Wrapper = new ERC721Wrapper(provider, addressesWithBalances, owner);
erc1155ProxyWrapper = new ERC1155ProxyWrapper(provider, addressesWithBalances, owner);
// Deploy ERC20 token & ERC20 proxy
const numDummyErc20ToDeploy = 4;
erc20Tokens = await erc20Wrapper.deployDummyTokensAsync(
numDummyErc20ToDeploy,
constants.DUMMY_TOKEN_DECIMALS,
);
erc20Tokens = await erc20Wrapper.deployDummyTokensAsync(numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS);
erc20Proxy = await erc20Wrapper.deployProxyAsync();
await erc20Wrapper.setBalancesAndAllowancesAsync();
// Deploy ERC721 token and proxy
[erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
erc721Proxy = await erc721Wrapper.deployProxyAsync();
await erc721Wrapper.setBalancesAndAllowancesAsync();
const erc721Balances = await erc721Wrapper.getBalancesAsync();
erc721LeftMakerAssetIds = erc721Balances[makerAddressLeft][erc721Token.address];
erc721RightMakerAssetIds = erc721Balances[makerAddressRight][erc721Token.address];
// Deploy ERC1155 token and proxy
[erc1155Wrapper] = await erc1155ProxyWrapper.deployDummyContractsAsync();
erc1155Token = erc1155Wrapper.getContract();
erc1155Proxy = await erc1155ProxyWrapper.deployProxyAsync();
await erc1155ProxyWrapper.setBalancesAndAllowancesAsync();
const erc1155Holdings = await erc1155ProxyWrapper.getBalancesAsync();
erc1155LeftMakerHoldings = getIndividualERC1155Holdings(
erc1155Holdings,
erc1155Token.address,
makerAddressLeft,
);
erc1155RightMakerHoldings = getIndividualERC1155Holdings(
erc1155Holdings,
erc1155Token.address,
makerAddressRight,
);
// Deploy MultiAssetProxy.
const multiAssetProxyContract = await MultiAssetProxyContract.deployFrom0xArtifactAsync(
assetProxyArtifacts.MultiAssetProxy,
@ -178,6 +155,11 @@ describe('matchOrders', () => {
{ from: owner },
constants.AWAIT_TRANSACTION_MINED_MS,
);
await multiAssetProxyContract.addAuthorizedAddress.awaitTransactionSuccessAsync(
exchange.address,
{ from: owner },
constants.AWAIT_TRANSACTION_MINED_MS,
);
await erc20Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync(
multiAssetProxyContract.address,
{ from: owner },
@ -193,8 +175,18 @@ describe('matchOrders', () => {
{ from: owner },
constants.AWAIT_TRANSACTION_MINED_MS,
);
await multiAssetProxyContract.addAuthorizedAddress.awaitTransactionSuccessAsync(
exchange.address,
await multiAssetProxyContract.registerAssetProxy.awaitTransactionSuccessAsync(
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 },
constants.AWAIT_TRANSACTION_MINED_MS,
);
@ -211,6 +203,7 @@ describe('matchOrders', () => {
defaultERC20TakerAssetAddress = erc20Tokens[1].address;
defaultFeeTokenAddress = erc20Tokens[2].address;
defaultERC721AssetAddress = erc721Token.address;
defaultERC1155AssetAddress = erc1155Token.address;
const domain = {
verifyingContractAddress: exchange.address,
chainId,
@ -240,17 +233,15 @@ describe('matchOrders', () => {
orderFactoryLeft = new OrderFactory(privateKeyLeft, defaultOrderParamsLeft);
const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)];
orderFactoryRight = new OrderFactory(privateKeyRight, defaultOrderParamsRight);
// Set match order tester
matchOrderTester = new MatchOrderTester(exchangeWrapper, erc20Wrapper, erc721Wrapper);
testExchange = await TestExchangeInternalsContract.deployFrom0xArtifactAsync(
artifacts.TestExchangeInternals,
provider,
txDefaults,
new BigNumber(chainId),
);
console.log(erc1155LeftMakerHoldings);
console.log(erc1155RightMakerHoldings);
// Create match order tester
matchOrderTester = new MatchOrderTester(exchangeWrapper, erc20Wrapper, erc721Wrapper, erc1155ProxyWrapper);
tokenBalances = await matchOrderTester.getBalancesAsync();
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
@ -1266,267 +1257,452 @@ describe('matchOrders', () => {
return expect(tx).to.revertWith(expectedError);
});
it('should transfer correct amounts when left order maker asset is an ERC721 token', async () => {
// 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', () => {
describe('combinations', () => {
// tslint:disable: enum-naming
enum TokenType {
ERC20A = 'ERC20A',
ERC20B = 'ERC20B',
ERC20C = 'ERC20C',
ERC20D = 'ERC20D',
ERC721A = 'ERC721A',
ERC721B = 'ERC721B',
ERC721C = 'ERC721C',
ERC721D = 'ERC721D',
enum AssetType {
ERC20A = 'ERC20_A',
ERC20B = 'ERC20_B',
ERC20C = 'ERC20_C',
ERC20D = 'ERC20_D',
ERC721LeftMaker = 'ERC721_LEFT_MAKER',
ERC721RightMaker = 'ERC721_RIGHT_MAKER',
ERC721Taker = 'ERC721_TAKER',
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 {
leftMaker: TokenType;
rightMaker: TokenType;
leftMakerFee: TokenType;
rightMakerFee: TokenType;
leftTakerFee: TokenType;
rightTakerFee: TokenType;
leftMaker: AssetType;
rightMaker: AssetType;
leftMakerFee: AssetType;
rightMakerFee: AssetType;
leftTakerFee: AssetType;
rightTakerFee: AssetType;
description?: string;
shouldFail?: boolean;
}
const feeAssetCombinations: AssetCombination[] = [
const assetCombinations: AssetCombination[] = [
{
description: 'Swapping tokens then using them to pay maker fees.',
leftMaker: TokenType.ERC20A,
rightMaker: TokenType.ERC20B,
leftMakerFee: TokenType.ERC20B,
rightMakerFee: TokenType.ERC20A,
leftTakerFee: TokenType.ERC20C,
rightTakerFee: TokenType.ERC20C,
leftMaker: AssetType.ERC20A,
rightMaker: AssetType.ERC20B,
leftMakerFee: AssetType.ERC20C,
rightMakerFee: AssetType.ERC20C,
leftTakerFee: AssetType.ERC20C,
rightTakerFee: AssetType.ERC20C,
},
{
description: 'Swapping tokens then using them to pay taker fees.',
leftMaker: TokenType.ERC20A,
rightMaker: TokenType.ERC20B,
leftMakerFee: TokenType.ERC20C,
rightMakerFee: TokenType.ERC20C,
leftTakerFee: TokenType.ERC20B,
rightTakerFee: TokenType.ERC20A,
leftMaker: AssetType.ERC721LeftMaker,
rightMaker: AssetType.ERC721RightMaker,
leftMakerFee: AssetType.ERC20C,
rightMakerFee: AssetType.ERC20C,
leftTakerFee: AssetType.ERC20C,
rightTakerFee: AssetType.ERC20C,
},
{
description: 'Swapping tokens then using them to pay maker and taker fees.',
leftMaker: TokenType.ERC20A,
rightMaker: TokenType.ERC20B,
leftMakerFee: TokenType.ERC20B,
rightMakerFee: TokenType.ERC20A,
leftTakerFee: TokenType.ERC20C,
rightTakerFee: TokenType.ERC20C,
leftMaker: AssetType.ERC721LeftMaker,
rightMaker: AssetType.ERC20A,
leftMakerFee: AssetType.ERC20C,
rightMakerFee: AssetType.ERC20C,
leftTakerFee: AssetType.ERC20C,
rightTakerFee: AssetType.ERC20C,
},
{
description: 'Paying maker and taker fees with same tokens being sold.',
leftMaker: TokenType.ERC20A,
rightMaker: TokenType.ERC20B,
leftMakerFee: TokenType.ERC20A,
rightMakerFee: TokenType.ERC20B,
leftTakerFee: TokenType.ERC20A,
rightTakerFee: TokenType.ERC20B,
leftMaker: AssetType.ERC20A,
rightMaker: AssetType.ERC721RightMaker,
leftMakerFee: AssetType.ERC20C,
rightMakerFee: AssetType.ERC20C,
leftTakerFee: AssetType.ERC20C,
rightTakerFee: AssetType.ERC20C,
},
{
description: 'Paying maker and taker fees with same tokens being bought.',
leftMaker: TokenType.ERC20A,
rightMaker: TokenType.ERC20B,
leftMakerFee: TokenType.ERC20B,
rightMakerFee: TokenType.ERC20A,
leftTakerFee: TokenType.ERC20B,
rightTakerFee: TokenType.ERC20A,
leftMaker: AssetType.ERC1155FungibleA,
rightMaker: AssetType.ERC20A,
leftMakerFee: AssetType.ERC20C,
rightMakerFee: AssetType.ERC20C,
leftTakerFee: AssetType.ERC20C,
rightTakerFee: AssetType.ERC20C,
},
{
description: 'Buy an ERC721 then use it to pay maker fees.',
leftMaker: TokenType.ERC20A,
rightMaker: TokenType.ERC721A,
leftMakerFee: TokenType.ERC721A,
rightMakerFee: TokenType.ERC20B,
leftTakerFee: TokenType.ERC20C,
rightTakerFee: TokenType.ERC20C,
leftMaker: AssetType.ERC20A,
rightMaker: AssetType.ERC1155FungibleB,
leftMakerFee: AssetType.ERC20C,
rightMakerFee: AssetType.ERC20C,
leftTakerFee: AssetType.ERC20C,
rightTakerFee: AssetType.ERC20C,
},
{
description: 'Buy an ERC721 then use it to pay maker fee (the other way).',
leftMaker: TokenType.ERC721A,
rightMaker: TokenType.ERC20A,
leftMakerFee: TokenType.ERC20B,
rightMakerFee: TokenType.ERC721A,
leftTakerFee: TokenType.ERC20C,
rightTakerFee: TokenType.ERC20C,
leftMaker: AssetType.ERC1155FungibleA,
rightMaker: AssetType.ERC1155FungibleA,
leftMakerFee: AssetType.ERC20C,
rightMakerFee: AssetType.ERC20C,
leftTakerFee: AssetType.ERC20C,
rightTakerFee: AssetType.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.',
leftMaker: TokenType.ERC721A,
rightMaker: TokenType.ERC721B,
leftMakerFee: TokenType.ERC721B,
rightMakerFee: TokenType.ERC721A,
leftTakerFee: TokenType.ERC20A,
rightTakerFee: TokenType.ERC20A,
leftMaker: AssetType.ERC721LeftMaker,
rightMaker: AssetType.ERC721RightMaker,
leftMakerFee: AssetType.ERC721RightMaker,
rightMakerFee: AssetType.ERC721LeftMaker,
leftTakerFee: AssetType.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).',
leftMaker: TokenType.ERC721A,
rightMaker: TokenType.ERC721B,
leftMakerFee: TokenType.ERC721A,
rightMakerFee: TokenType.ERC721A,
leftTakerFee: TokenType.ERC20A,
rightTakerFee: TokenType.ERC20A,
leftMaker: AssetType.ERC721LeftMaker,
rightMaker: AssetType.ERC721RightMaker,
leftMakerFee: AssetType.ERC721LeftMaker,
rightMakerFee: AssetType.ERC721LeftMaker,
leftTakerFee: AssetType.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,
},
];
let erc721TokenIdsByOwner: ERC721TokenIdsByOwner;
let nameToERC20Tokens: { [name: string]: string };
let nameToERC721Tokens: { [name: string]: string };
let nameToERC20Asset: { [name: string]: string };
let nameToERC721Asset: { [name: string]: [string, BigNumber] };
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 encodeERC721AssetData = assetDataUtils.encodeERC721AssetData;
if (nameToERC20Tokens[tokenType] !== undefined) {
const tokenAddress = nameToERC20Tokens[tokenType];
const encodeERC1155AssetData = assetDataUtils.encodeERC1155AssetData;
const encodeMultiAssetData = assetDataUtils.encodeMultiAssetData;
if (nameToERC20Asset[assetType] !== undefined) {
const tokenAddress = nameToERC20Asset[assetType];
return encodeERC20AssetData(tokenAddress);
}
if (nameToERC721Tokens[tokenType] !== undefined) {
const tokenAddress = nameToERC721Tokens[tokenType];
const tokenIdx = tokenType.charCodeAt(tokenType.length - 1) - 'A'.charCodeAt(0);
const tokenId = erc721TokenIdsByOwner[ownerAddress][tokenAddress][tokenIdx];
return encodeERC721AssetData(nameToERC721Tokens[tokenType], tokenId);
if (nameToERC721Asset[assetType] !== undefined) {
const [tokenAddress, tokenId] = nameToERC721Asset[assetType];
return encodeERC721AssetData(tokenAddress, 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 () => {
erc721TokenIdsByOwner = await erc721Wrapper.getBalancesAsync();
nameToERC20Tokens = {
ERC20A: erc20Tokens[0].address,
ERC20B: erc20Tokens[1].address,
ERC20C: erc20Tokens[0].address,
ERC20D: erc20Tokens[1].address,
nameToERC20Asset = {
ERC20_A: erc20Tokens[0].address,
ERC20_B: erc20Tokens[1].address,
ERC20_C: erc20Tokens[2].address,
ERC20_D: erc20Tokens[3].address,
};
nameToERC721Tokens = {
ERC721A: erc721Token.address,
ERC721B: erc721Token.address,
ERC721C: erc721Token.address,
ERC721D: erc721Token.address,
const erc721TokenIds = _.mapValues(tokenBalances.erc721, v => v[defaultERC721AssetAddress.address][0]);
nameToERC721Asset = {
ERC721_LEFT_MAKER: [defaultERC721AssetAddress.address, erc721TokenIds[makerAddressLeft]],
ERC721_RIGHT_MAKER: [defaultERC721AssetAddress.address, erc721TokenIds[makerAddressRight]],
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);
it(description, async () => {
// 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(1, 0);
const leftTakerAssetAmount = combo.rightMaker.startsWith('ERC20')
const leftTakerAssetAmount = _.includes(fungibleTypes, combo.rightMaker)
? Web3Wrapper.toBaseUnitAmount(30, 18)
: Web3Wrapper.toBaseUnitAmount(1, 0);
const rightMakerAssetAmount = combo.rightMaker.startsWith('ERC20')
const rightMakerAssetAmount = _.includes(fungibleTypes, combo.rightMaker)
? Web3Wrapper.toBaseUnitAmount(30, 18)
: Web3Wrapper.toBaseUnitAmount(1, 0);
const rightTakerAssetAmount = combo.leftMaker.startsWith('ERC20')
const rightTakerAssetAmount = _.includes(fungibleTypes, combo.leftMaker)
? Web3Wrapper.toBaseUnitAmount(14, 18)
: Web3Wrapper.toBaseUnitAmount(1, 0);
const leftMakerFeeAssetAmount = combo.leftMakerFee.startsWith('ERC20')
const leftMakerFeeAssetAmount = _.includes(fungibleTypes, combo.leftMakerFee)
? Web3Wrapper.toBaseUnitAmount(8, 12)
: Web3Wrapper.toBaseUnitAmount(1, 0);
const rightMakerFeeAssetAmount = combo.rightMakerFee.startsWith('ERC20')
const rightMakerFeeAssetAmount = _.includes(fungibleTypes, combo.rightMakerFee)
? Web3Wrapper.toBaseUnitAmount(7, 12)
: Web3Wrapper.toBaseUnitAmount(1, 0);
const leftTakerFeeAssetAmount = combo.leftTakerFee.startsWith('ERC20')
const leftTakerFeeAssetAmount = _.includes(fungibleTypes, combo.leftTakerFee)
? Web3Wrapper.toBaseUnitAmount(6, 12)
: Web3Wrapper.toBaseUnitAmount(1, 0);
const rightTakerFeeAssetAmount = combo.rightTakerFee.startsWith('ERC20')
const rightTakerFeeAssetAmount = _.includes(fungibleTypes, combo.rightTakerFee)
? Web3Wrapper.toBaseUnitAmount(5, 12)
: Web3Wrapper.toBaseUnitAmount(1, 0);
const leftMakerAssetReceivedByTakerAmount = combo.leftMaker.startsWith('ERC20')
const leftMakerAssetReceivedByTakerAmount = _.includes(fungibleTypes, combo.leftMaker)
? leftMakerAssetAmount.minus(rightTakerAssetAmount)
: Web3Wrapper.toBaseUnitAmount(0, 0);
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
makerAssetData: getAssetData(combo.leftMaker, makerAddressLeft),
takerAssetData: getAssetData(combo.rightMaker, makerAddressRight),
makerFeeAssetData: getAssetData(combo.leftMakerFee, makerAddressLeft),
takerFeeAssetData: getAssetData(combo.leftTakerFee, takerAddress),
makerAssetData: getAssetData(combo.leftMaker),
takerAssetData: getAssetData(combo.rightMaker),
makerFeeAssetData: getAssetData(combo.leftMakerFee),
takerFeeAssetData: getAssetData(combo.leftTakerFee),
makerAssetAmount: leftMakerAssetAmount,
takerAssetAmount: leftTakerAssetAmount,
makerFee: leftMakerFeeAssetAmount,
takerFee: leftTakerFeeAssetAmount,
});
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
makerAssetData: getAssetData(combo.rightMaker, makerAddressRight),
takerAssetData: getAssetData(combo.leftMaker, makerAddressLeft),
makerFeeAssetData: getAssetData(combo.rightMakerFee, makerAddressRight),
takerFeeAssetData: getAssetData(combo.rightTakerFee, takerAddress),
makerAssetData: getAssetData(combo.rightMaker),
takerAssetData: getAssetData(combo.leftMaker),
makerFeeAssetData: getAssetData(combo.rightMakerFee),
takerFeeAssetData: getAssetData(combo.rightTakerFee),
makerAssetAmount: rightMakerAssetAmount,
takerAssetAmount: rightTakerAssetAmount,
makerFee: rightMakerFeeAssetAmount,
@ -1536,15 +1712,15 @@ describe('matchOrders', () => {
const expectedTransferAmounts = {
// Left Maker
leftMakerAssetSoldByLeftMakerAmount: leftMakerAssetAmount,
leftMakerFeeAssetPaidByLeftMakerAmount: leftMakerFeeAssetAmount, // 100%
leftMakerFeeAssetPaidByLeftMakerAmount: leftMakerFeeAssetAmount,
// Right Maker
rightMakerAssetSoldByRightMakerAmount: rightMakerAssetAmount,
leftMakerAssetBoughtByRightMakerAmount: rightTakerAssetAmount,
rightMakerFeeAssetPaidByRightMakerAmount: rightMakerFeeAssetAmount, // 100%
rightMakerFeeAssetPaidByRightMakerAmount: rightMakerFeeAssetAmount,
// Taker
leftMakerAssetReceivedByTakerAmount,
leftTakerFeeAssetPaidByTakerAmount: leftTakerFeeAssetAmount, // 100%
rightTakerFeeAssetPaidByTakerAmount: rightTakerFeeAssetAmount, // 100%
leftTakerFeeAssetPaidByTakerAmount: leftTakerFeeAssetAmount,
rightTakerFeeAssetPaidByTakerAmount: rightTakerFeeAssetAmount,
};
if (!combo.shouldFail) {
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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import { ERC20Wrapper, ERC721Wrapper } from '@0x/contracts-asset-proxy';
import { chaiSetup, OrderStatus, TokenBalancesByOwner } from '@0x/contracts-test-utils';
import { ERC1155ProxyWrapper, ERC20Wrapper, ERC721Wrapper } from '@0x/contracts-asset-proxy';
import { chaiSetup, ERC1155HoldingsByOwner, OrderStatus } from '@0x/contracts-test-utils';
import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
import { AssetProxyId, SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
@ -14,6 +14,13 @@ const ZERO = new BigNumber(0);
chaiSetup.configure();
const expect = chai.expect;
export interface IndividualERC1155Holdings {
fungible: {
[tokenId: string]: BigNumber;
};
nonFungible: BigNumber[];
}
export interface FillEventArgs {
orderHash: string;
makerAddress: string;
@ -46,7 +53,32 @@ export interface MatchTransferAmounts {
export interface MatchResults {
orders: MatchedOrders;
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 {
@ -66,14 +98,16 @@ export class MatchOrderTester {
public exchangeWrapper: ExchangeWrapper;
public erc20Wrapper: ERC20Wrapper;
public erc721Wrapper: ERC721Wrapper;
public erc1155ProxyWrapper: ERC1155ProxyWrapper;
public matchOrdersCallAsync?: MatchOrdersAsyncCall;
private readonly _initialTokenBalancesPromise: Promise<TokenBalancesByOwner>;
private readonly _initialTokenBalancesPromise: Promise<TokenBalances>;
/**
* Constructs new MatchOrderTester.
* @param exchangeWrapper Used to call to the Exchange.
* @param erc20Wrapper Used to fetch ERC20 balances.
* @param erc721Wrapper Used to fetch ERC721 token owners.
* @param erc1155Wrapper Used to fetch ERC1155 token owners.
* @param matchOrdersCallAsync Optional, custom caller for
* `ExchangeWrapper.matchOrdersAsync()`.
*/
@ -81,11 +115,13 @@ export class MatchOrderTester {
exchangeWrapper: ExchangeWrapper,
erc20Wrapper: ERC20Wrapper,
erc721Wrapper: ERC721Wrapper,
erc1155ProxyWrapper: ERC1155ProxyWrapper,
matchOrdersCallAsync?: MatchOrdersAsyncCall,
) {
this.exchangeWrapper = exchangeWrapper;
this.erc20Wrapper = erc20Wrapper;
this.erc721Wrapper = erc721Wrapper;
this.erc1155ProxyWrapper = erc1155ProxyWrapper;
this.matchOrdersCallAsync = matchOrdersCallAsync;
this._initialTokenBalancesPromise = this.getBalancesAsync();
}
@ -103,7 +139,7 @@ export class MatchOrderTester {
orders: MatchedOrders,
takerAddress: string,
expectedTransferAmounts: Partial<MatchTransferAmounts>,
initialTokenBalances?: TokenBalancesByOwner,
initialTokenBalances?: TokenBalances,
): Promise<MatchResults> {
await assertInitialOrderStatesAsync(orders, this.exchangeWrapper);
// Get the token balances before executing `matchOrders()`.
@ -136,8 +172,8 @@ export class MatchOrderTester {
/**
* Fetch the current token balances of all known accounts.
*/
public async getBalancesAsync(): Promise<TokenBalancesByOwner> {
return getTokenBalancesAsync(this.erc20Wrapper, this.erc721Wrapper);
public async getBalancesAsync(): Promise<TokenBalances> {
return getTokenBalancesAsync(this.erc20Wrapper, this.erc721Wrapper, this.erc1155ProxyWrapper);
}
private async _executeMatchOrdersAsync(
@ -203,7 +239,7 @@ function toFullMatchTransferAmounts(partial: Partial<MatchTransferAmounts>): Mat
function simulateMatchOrders(
orders: MatchedOrders,
takerAddress: string,
tokenBalances: TokenBalancesByOwner,
tokenBalances: TokenBalances,
transferAmounts: MatchTransferAmounts,
): MatchResults {
// prettier-ignore
@ -336,8 +372,47 @@ function transferAsset(
const tokenId = erc721AssetData.tokenId;
const fromTokens = matchResults.balances.erc721[fromAddress][assetAddress];
const toTokens = matchResults.balances.erc721[toAddress][assetAddress];
_.remove(fromTokens, tokenId);
toTokens.push(tokenId);
if (amount.gte(1)) {
const tokenIndex = _.findIndex(fromTokens, t => t.eq(tokenId));
if (tokenIndex !== -1) {
fromTokens.splice(tokenIndex, 1);
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;
}
default:
@ -355,7 +430,7 @@ function transferAsset(
async function assertMatchResultsAsync(
matchResults: MatchResults,
transactionReceipt: TransactionReceiptWithDecodedLogs,
actualTokenBalances: TokenBalancesByOwner,
actualTokenBalances: TokenBalances,
exchangeWrapper: ExchangeWrapper,
): Promise<void> {
// Check the fill events.
@ -462,21 +537,8 @@ function extractFillEventsfromReceipt(receipt: TransactionReceiptWithDecodedLogs
* @param expectedBalances Expected balances.
* @param actualBalances Actual balances.
*/
function assertBalances(expectedBalances: TokenBalancesByOwner, actualBalances: TokenBalancesByOwner): void {
// ERC20 Balances
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);
function assertBalances(expectedBalances: TokenBalances, actualBalances: TokenBalances): void {
expect(encodeTokenBalances(expectedBalances)).to.deep.equal(encodeTokenBalances(actualBalances));
}
/**
@ -533,17 +595,57 @@ async function assertPostExchangeStateAsync(
* Retrive the current token balances of all known addresses.
* @param erc20Wrapper The ERC20Wrapper 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(
erc20Wrapper: ERC20Wrapper,
erc721Wrapper: ERC721Wrapper,
): Promise<TokenBalancesByOwner> {
const [erc20, erc721] = await Promise.all([erc20Wrapper.getBalancesAsync(), erc721Wrapper.getBalancesAsync()]);
erc1155ProxyWrapper: ERC1155ProxyWrapper,
): Promise<TokenBalances> {
const [erc20, erc721, erc1155] = await Promise.all([
erc20Wrapper.getBalancesAsync(),
erc721Wrapper.getBalancesAsync(),
erc1155ProxyWrapper.getBalancesAsync(),
]);
return {
erc20,
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