diff --git a/contracts/exchange/test/fill_order.ts b/contracts/exchange/test/fill_order.ts index 3f4eddb53a..117dcbd424 100644 --- a/contracts/exchange/test/fill_order.ts +++ b/contracts/exchange/test/fill_order.ts @@ -9,7 +9,6 @@ import { ExpirationTimeSecondsScenario, FeeAssetDataScenario, FeeRecipientAddressScenario, - FillScenario, OrderAssetAmountScenario, TakerAssetFillAmountScenario, TakerScenario, @@ -52,7 +51,7 @@ const defaultFillScenario = { }, }; -describe('FillOrder Tests', () => { +describe.only('FillOrder Tests', () => { let fillOrderCombinatorialUtils: FillOrderCombinatorialUtils; before(async () => { @@ -68,26 +67,7 @@ describe('FillOrder Tests', () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe('fillOrder', () => { - const test = (fillScenarios: FillScenario[]) => { - _.forEach(fillScenarios, fillScenario => { - const description = `Combinatorial OrderFill: ${JSON.stringify(fillScenario)}`; - it(description, async () => { - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(fillScenario); - }); - }); - }; - - const allFillScenarios = FillOrderCombinatorialUtils.generateFillOrderCombinations(); - describe('Combinatorially generated fills orders', () => test(allFillScenarios)); - - it('should transfer the correct amounts when makerAssetAmount === takerAssetAmount', async () => { - const fillScenario = { - ...defaultFillScenario, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario); - }); - + describe('Fill tests', () => { it('should transfer the correct amounts when makerAssetAmount > takerAssetAmount', async () => { const fillScenario = { ...defaultFillScenario, @@ -141,38 +121,6 @@ describe('FillOrder Tests', () => { await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario); }); - it('should be able to pay maker fee with taker asset', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerFeeAssetDataScenario: FeeAssetDataScenario.TakerToken, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, - makerStateScenario: { - ...defaultFillScenario.makerStateScenario, - feeBalance: BalanceAmountScenario.Zero, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario); - }); - - it('should be able to pay taker fee with maker asset', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - takerFeeAssetDataScenario: FeeAssetDataScenario.MakerToken, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, - takerStateScenario: { - ...defaultFillScenario.takerStateScenario, - feeBalance: BalanceAmountScenario.Zero, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario); - }); - it('should throw when taker is specified and order is claimed by other', async () => { const fillScenario = { ...defaultFillScenario, @@ -226,8 +174,92 @@ describe('FillOrder Tests', () => { }; await fillOrderCombinatorialUtils.testFillOrderScenarioFailureAsync(fillScenario); }); + }); - it('should throw if maker erc20Balances are too low to fill order', async () => { + describe('ERC20', () => { + it('should be able to pay maker fee with taker asset', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerFeeAssetDataScenario: FeeAssetDataScenario.TakerToken, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, + makerStateScenario: { + ...defaultFillScenario.makerStateScenario, + feeBalance: BalanceAmountScenario.Zero, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario); + }); + + it('should be able to pay taker fee with maker asset', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + takerFeeAssetDataScenario: FeeAssetDataScenario.MakerToken, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, + takerStateScenario: { + ...defaultFillScenario.takerStateScenario, + feeBalance: BalanceAmountScenario.Zero, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario); + }); + + it('should not be able to pay maker fee with maker asset if none is left over (double-spend)', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerFeeAssetDataScenario: FeeAssetDataScenario.MakerToken, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, + makerStateScenario: { + ...defaultFillScenario.makerStateScenario, + traderAssetBalance: BalanceAmountScenario.Exact, + feeBalance: BalanceAmountScenario.Zero, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioFailureAsync(fillScenario); + }); + + it('should not be able to pay taker fee with taker asset if none is left over (double-spend)', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + takerFeeAssetDataScenario: FeeAssetDataScenario.TakerToken, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, + takerStateScenario: { + ...defaultFillScenario.takerStateScenario, + traderAssetBalance: BalanceAmountScenario.Exact, + feeBalance: BalanceAmountScenario.Zero, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioFailureAsync(fillScenario); + }); + + it('should be able to pay taker fee with maker asset', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + takerFeeAssetDataScenario: FeeAssetDataScenario.MakerToken, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, + takerStateScenario: { + ...defaultFillScenario.takerStateScenario, + feeBalance: BalanceAmountScenario.Zero, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario); + }); + + it('should throw if maker balance is too low to fill order', async () => { const fillScenario = { ...defaultFillScenario, makerStateScenario: { @@ -238,7 +270,7 @@ describe('FillOrder Tests', () => { await fillOrderCombinatorialUtils.testFillOrderScenarioFailureAsync(fillScenario); }); - it('should throw if taker erc20Balances are too low to fill order', async () => { + it('should throw if taker balance is too low to fill order', async () => { const fillScenario = { ...defaultFillScenario, takerStateScenario: { @@ -271,7 +303,7 @@ describe('FillOrder Tests', () => { await fillOrderCombinatorialUtils.testFillOrderScenarioFailureAsync(fillScenario); }); - it('should throw if maker fee erc20Balances are too low to fill order', async () => { + it('should throw if maker fee balance is too low to fill order', async () => { const fillScenario = { ...defaultFillScenario, makerStateScenario: { @@ -282,7 +314,7 @@ describe('FillOrder Tests', () => { await fillOrderCombinatorialUtils.testFillOrderScenarioFailureAsync(fillScenario); }); - it('should throw if taker fee erc20Balances are too low to fill order', async () => { + it('should throw if taker fee balance is too low to fill order', async () => { const fillScenario = { ...defaultFillScenario, takerStateScenario: { @@ -316,85 +348,8 @@ describe('FillOrder Tests', () => { }); }); - describe('Testing exchange of ERC721 Tokens', () => { - it('should successfully exchange a single token between the maker and taker (via fillOrder)', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetDataScenario: AssetDataScenario.ERC721, - takerAssetDataScenario: AssetDataScenario.ERC721, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario); - }); - - it('should successfully fill order when makerAsset is ERC721 and takerAsset is ERC20', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetDataScenario: AssetDataScenario.ERC721, - takerAssetDataScenario: AssetDataScenario.ERC20EighteenDecimals, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario); - }); - - it('should successfully fill order when makerAsset is ERC20 and takerAsset is ERC721', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetDataScenario: AssetDataScenario.ERC20EighteenDecimals, - takerAssetDataScenario: AssetDataScenario.ERC721, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario); - }); - - it('should successfully fill order when makerAsset is ERC721 and approveAll is set for it', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetDataScenario: AssetDataScenario.ERC721, - takerAssetDataScenario: AssetDataScenario.ERC20EighteenDecimals, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, - makerStateScenario: { - ...defaultFillScenario.makerStateScenario, - traderAssetAllowance: AllowanceAmountScenario.Unlimited, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario); - }); - - it('should successfully fill order when makerAsset and takerAsset are ERC721 and approveAll is set for them', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetDataScenario: AssetDataScenario.ERC721, - takerAssetDataScenario: AssetDataScenario.ERC721, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, - makerStateScenario: { - ...defaultFillScenario.makerStateScenario, - traderAssetAllowance: AllowanceAmountScenario.Unlimited, - }, - takerStateScenario: { - ...defaultFillScenario.takerStateScenario, - traderAssetAllowance: AllowanceAmountScenario.Unlimited, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario); - }); - - it('should be able to pay maker fee with taker asset', async () => { + describe('ERC721', () => { + it('should be able to pay maker fee with taker ERC721', async () => { const fillScenario = { ...defaultFillScenario, orderScenario: { @@ -406,13 +361,12 @@ describe('FillOrder Tests', () => { makerStateScenario: { ...defaultFillScenario.makerStateScenario, feeBalance: BalanceAmountScenario.Zero, - feeAllowance: AllowanceAmountScenario.Unlimited, }, }; await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario); }); - it('should be able to pay taker fee with maker asset', async () => { + it('should be able to pay taker fee with maker ERC721', async () => { const fillScenario = { ...defaultFillScenario, orderScenario: { @@ -424,10 +378,259 @@ describe('FillOrder Tests', () => { takerStateScenario: { ...defaultFillScenario.takerStateScenario, feeBalance: BalanceAmountScenario.Zero, - feeAllowance: AllowanceAmountScenario.Unlimited, }, }; await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario); }); + + it('should not be able to pay maker fee with maker ERC721 (double-spend)', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetDataScenario: AssetDataScenario.ERC721, + makerFeeAssetDataScenario: FeeAssetDataScenario.MakerToken, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, + makerStateScenario: { + ...defaultFillScenario.makerStateScenario, + feeBalance: BalanceAmountScenario.Zero, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioFailureAsync(fillScenario); + }); + + it('should be able to pay taker fee with taker ERC721 (double-spend)', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + takerAssetDataScenario: AssetDataScenario.ERC721, + takerFeeAssetDataScenario: FeeAssetDataScenario.TakerToken, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, + takerStateScenario: { + ...defaultFillScenario.takerStateScenario, + feeBalance: BalanceAmountScenario.Zero, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioFailureAsync(fillScenario); + }); + }); + + describe('ERC1155', () => { + const assetTypes = [AssetDataScenario.ERC1155Fungible, AssetDataScenario.ERC1155NonFungible]; + for (const assetType of assetTypes) { + describe(_.startCase(_.toLower((/ERC1155(.+)/.exec(assetType) as string[])[1])), () => { + it('should be able to pay maker fee with taker asset', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + takerAssetDataScenario: assetType, + makerFeeAssetDataScenario: FeeAssetDataScenario.TakerToken, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, + makerStateScenario: { + ...defaultFillScenario.makerStateScenario, + feeBalance: BalanceAmountScenario.Zero, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario); + }); + + it('should be able to pay taker fee with maker asset', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetDataScenario: assetType, + takerFeeAssetDataScenario: FeeAssetDataScenario.MakerToken, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, + takerStateScenario: { + ...defaultFillScenario.takerStateScenario, + feeBalance: BalanceAmountScenario.Zero, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario); + }); + + it('should not be able to pay maker fee with maker asset if not enough left (double-spend)', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetDataScenario: assetType, + makerFeeAssetDataScenario: FeeAssetDataScenario.MakerToken, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, + makerStateScenario: { + ...defaultFillScenario.makerStateScenario, + traderAssetBalance: BalanceAmountScenario.Exact, + feeBalance: BalanceAmountScenario.Zero, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioFailureAsync(fillScenario); + }); + + it('should be able to pay taker fee with taker asset if not enough left (double-spend)', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + takerAssetDataScenario: assetType, + takerFeeAssetDataScenario: FeeAssetDataScenario.TakerToken, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, + takerStateScenario: { + ...defaultFillScenario.takerStateScenario, + traderAssetBalance: BalanceAmountScenario.Exact, + feeBalance: BalanceAmountScenario.Zero, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioFailureAsync(fillScenario); + }); + }); + } + }); + + describe('MultiAssetProxy', () => { + it('should be able to pay maker fee with taker MAP', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + takerAssetDataScenario: AssetDataScenario.MultiAssetERC20, + makerFeeAssetDataScenario: FeeAssetDataScenario.TakerToken, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, + makerStateScenario: { + ...defaultFillScenario.makerStateScenario, + feeBalance: BalanceAmountScenario.Zero, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario); + }); + + it('should be able to pay taker fee with maker MAP', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetDataScenario: AssetDataScenario.MultiAssetERC20, + takerFeeAssetDataScenario: FeeAssetDataScenario.MakerToken, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, + takerStateScenario: { + ...defaultFillScenario.takerStateScenario, + feeBalance: BalanceAmountScenario.Zero, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario); + }); + + it('should not be able to pay maker fee with maker MAP (double-spend)', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetDataScenario: AssetDataScenario.MultiAssetERC20, + makerFeeAssetDataScenario: FeeAssetDataScenario.MakerToken, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, + makerStateScenario: { + ...defaultFillScenario.makerStateScenario, + feeBalance: BalanceAmountScenario.Zero, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioFailureAsync(fillScenario); + }); + + it('should be able to pay taker fee with taker MAP (double-spend)', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + takerAssetDataScenario: AssetDataScenario.MultiAssetERC20, + takerFeeAssetDataScenario: FeeAssetDataScenario.TakerToken, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, + takerStateScenario: { + ...defaultFillScenario.takerStateScenario, + feeBalance: BalanceAmountScenario.Zero, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioFailureAsync(fillScenario); + }); + }); + + describe('Maker/taker asset combinations', () => { + const assetDataScenarios = [ + AssetDataScenario.ERC20EighteenDecimals, + AssetDataScenario.ERC721, + AssetDataScenario.ERC1155Fungible, + AssetDataScenario.ERC1155NonFungible, + AssetDataScenario.MultiAssetERC20, + ]; + for (const [makerAssetData, takerAssetData] of getAllPossiblePairs(assetDataScenarios)) { + it(`should successfully exchange ${makerAssetData} for ${takerAssetData}`, async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetDataScenario: makerAssetData, + takerAssetDataScenario: takerAssetData, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario); + }); + } + }); + + describe('Maker/taker fee asset combinations', () => { + const feeAssetDataScenarios = [ + 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 () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerFeeAssetDataScenario: makerFeeAssetData, + takerFeeAssetDataScenario: takerFeeAssetData, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyTakerAssetAmount, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioSuccessAsync(fillScenario); + }); + } + }); + + describe('Combinatorially generated fills orders', () => { + const allFillScenarios = FillOrderCombinatorialUtils.generateFillOrderCombinations(); + for (const fillScenario of allFillScenarios) { + const description = `Combinatorial OrderFill: ${JSON.stringify(fillScenario)}`; + it(description, async () => { + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(fillScenario); + }); + } }); }); + +function getAllPossiblePairs(choices: T[]): Array<[T, T]> { + const pairs: Array<[T, T]> = []; + for (const i of _.times(choices.length)) { + for (const j of _.times(choices.length)) { + pairs.push([choices[i], choices[j]]); + } + } + return pairs; +} +// tslint:disable: max-file-line-count diff --git a/contracts/exchange/test/utils/asset_wrapper.ts b/contracts/exchange/test/utils/asset_wrapper.ts index 5b51002b6a..c3f85c3c3b 100644 --- a/contracts/exchange/test/utils/asset_wrapper.ts +++ b/contracts/exchange/test/utils/asset_wrapper.ts @@ -4,20 +4,25 @@ import { AssetProxyId } from '@0x/types'; import { BigNumber, errorUtils } from '@0x/utils'; import * as _ from 'lodash'; -import { ERC20Wrapper, ERC721Wrapper } from '@0x/contracts-asset-proxy'; +import { ERC1155ProxyWrapper, ERC20Wrapper, ERC721Wrapper } from '@0x/contracts-asset-proxy'; interface ProxyIdToAssetWrappers { [proxyId: string]: AbstractAssetWrapper; } +const ONE_NFT_UNIT = new BigNumber(1); +const ZERO_NFT_UNIT = new BigNumber(0); + /** * This class abstracts away the differences between ERC20 and ERC721 tokens so that * the logic that uses it does not need to care what standard a token belongs to. */ export class AssetWrapper { private readonly _proxyIdToAssetWrappers: ProxyIdToAssetWrappers; - constructor(assetWrappers: AbstractAssetWrapper[]) { + private readonly _burnerAddress: string; + constructor(assetWrappers: AbstractAssetWrapper[], burnerAddress: string) { this._proxyIdToAssetWrappers = {}; + this._burnerAddress = burnerAddress; _.each(assetWrappers, assetWrapper => { const proxyId = assetWrapper.getProxyId(); this._proxyIdToAssetWrappers[proxyId] = assetWrapper; @@ -41,9 +46,31 @@ export class AssetWrapper { assetProxyData.tokenAddress, assetProxyData.tokenId, ); - const balance = isOwner ? new BigNumber(1) : new BigNumber(0); + const balance = isOwner ? ONE_NFT_UNIT : ZERO_NFT_UNIT; return balance; } + case AssetProxyId.ERC1155: { + // tslint:disable-next-line:no-unnecessary-type-assertion + const assetProxyWrapper = this._proxyIdToAssetWrappers[proxyId] as ERC1155ProxyWrapper; + const assetProxyData = assetDataUtils.decodeERC1155AssetData(assetData); + const assetWrapper = assetProxyWrapper.getContractWrapper(assetProxyData.tokenAddress); + const balances = await Promise.all( + _.map(assetProxyData.tokenIds).map(tokenId => assetWrapper.getBalanceAsync(userAddress, tokenId)), + ); + return BigNumber.min(...balances); + } + case AssetProxyId.MultiAsset: { + const assetProxyData = assetDataUtils.decodeMultiAssetData(assetData); + const nestedBalances = await Promise.all( + assetProxyData.nestedAssetData.map(async nestedAssetData => + this.getBalanceAsync(userAddress, nestedAssetData), + ), + ); + const scaledBalances = _.zip(assetProxyData.amounts, nestedBalances).map(([amount, balance]) => + (balance as BigNumber).div(amount as BigNumber).integerValue(BigNumber.ROUND_HALF_UP), + ); + return BigNumber.min(...scaledBalances); + } default: throw errorUtils.spawnSwitchErr('proxyId', proxyId); } @@ -54,13 +81,14 @@ export class AssetWrapper { case AssetProxyId.ERC20: { // tslint:disable-next-line:no-unnecessary-type-assertion const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper; - await erc20Wrapper.setBalanceAsync(userAddress, assetData, desiredBalance); + await erc20Wrapper.setBalanceAsync( + userAddress, + assetData, + desiredBalance.integerValue(BigNumber.ROUND_DOWN), + ); return; } case AssetProxyId.ERC721: { - if (!desiredBalance.eq(0) && !desiredBalance.eq(1)) { - throw new Error(`Balance for ERC721 token can only be set to 0 or 1. Got: ${desiredBalance}`); - } // tslint:disable-next-line:no-unnecessary-type-assertion const erc721Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper; const assetProxyData = assetDataUtils.decodeERC721AssetData(assetData); @@ -68,42 +96,137 @@ export class AssetWrapper { assetProxyData.tokenAddress, assetProxyData.tokenId, ); - if (!doesTokenExist && desiredBalance.eq(1)) { + if (!doesTokenExist && desiredBalance.gt(0)) { await erc721Wrapper.mintAsync(assetProxyData.tokenAddress, assetProxyData.tokenId, userAddress); return; - } else if (!doesTokenExist && desiredBalance.eq(0)) { + } else if (!doesTokenExist && desiredBalance.lte(0)) { return; // noop } const tokenOwner = await erc721Wrapper.ownerOfAsync( assetProxyData.tokenAddress, assetProxyData.tokenId, ); - if (userAddress !== tokenOwner && desiredBalance.eq(1)) { + if (userAddress !== tokenOwner && desiredBalance.gt(0)) { await erc721Wrapper.transferFromAsync( assetProxyData.tokenAddress, assetProxyData.tokenId, tokenOwner, userAddress, ); - } else if (tokenOwner === userAddress && desiredBalance.eq(0)) { - // Transfer token to someone else - const userAddresses = await (erc721Wrapper as any)._web3Wrapper.getAvailableAddressesAsync(); - const nonOwner = _.find(userAddresses, a => a !== userAddress); + } else if (tokenOwner === userAddress && desiredBalance.lte(0)) { + // Burn token await erc721Wrapper.transferFromAsync( assetProxyData.tokenAddress, assetProxyData.tokenId, tokenOwner, - nonOwner, + this._burnerAddress, ); return; } else if ( - (userAddress !== tokenOwner && desiredBalance.eq(0)) || - (tokenOwner === userAddress && desiredBalance.eq(1)) + (userAddress !== tokenOwner && desiredBalance.lte(0)) || + (tokenOwner === userAddress && desiredBalance.gt(0)) ) { return; // noop } break; } + case AssetProxyId.ERC1155: { + // tslint:disable-next-line:no-unnecessary-type-assertion + const assetProxyWrapper = this._proxyIdToAssetWrappers[proxyId] as ERC1155ProxyWrapper; + const assetProxyData = assetDataUtils.decodeERC1155AssetData(assetData); + const assetWrapper = assetProxyWrapper.getContractWrapper(assetProxyData.tokenAddress); + const tokenValuesSum = BigNumber.sum(...assetProxyData.tokenValues); + let tokenValueRatios = assetProxyData.tokenValues; + if (!tokenValuesSum.eq(0)) { + tokenValueRatios = assetProxyData.tokenValues.map(v => v.div(tokenValuesSum)); + } + for (const i of _.times(assetProxyData.tokenIds.length)) { + const tokenId = assetProxyData.tokenIds[i]; + const tokenValueRatio = tokenValueRatios[i]; + const scaledDesiredBalance = desiredBalance.times(tokenValueRatio); + const isFungible = await assetWrapper.isFungibleItemAsync(tokenId); + if (isFungible) { + // Token is fungible. + const currentBalance = await assetWrapper.getBalanceAsync(userAddress, tokenId); + const difference = scaledDesiredBalance + .minus(currentBalance) + .integerValue(BigNumber.ROUND_DOWN); + if (difference.eq(0)) { + // Just right. Nothing to do. + } else if (difference.lt(0)) { + // Too much. Burn some tokens. + await assetWrapper.safeTransferFromAsync( + userAddress, + this._burnerAddress, + tokenId, + difference.abs(), + ); + } else { + // difference.gt(0) + // Too little. Mint some tokens. + await assetWrapper.mintKnownFungibleTokensAsync(tokenId, [userAddress], [difference]); + } + } else { + const nftOwner = await assetWrapper.getOwnerOfAsync(tokenId); + if (scaledDesiredBalance.gt(0)) { + if (nftOwner === userAddress) { + // Nothing to do. + } else if (nftOwner !== constants.NULL_ADDRESS) { + // Transfer from current owner. + await assetWrapper.safeTransferFromAsync(nftOwner, userAddress, tokenId, ONE_NFT_UNIT); + } else { + throw new Error(`Cannot mint new ERC1155 tokens with a specific token ID.`); + } + } else { + if (nftOwner === userAddress) { + // Burn the token. + await assetWrapper.safeTransferFromAsync( + userAddress, + this._burnerAddress, + tokenId, + ONE_NFT_UNIT, + ); + } else { + // Nothing to do. + } + } + } + } + break; + } + case AssetProxyId.MultiAsset: { + const assetProxyData = assetDataUtils.decodeMultiAssetData(assetData); + const amountsSum = BigNumber.sum(...assetProxyData.amounts); + let assetAmountRatios = assetProxyData.amounts; + if (!amountsSum.eq(0)) { + assetAmountRatios = assetProxyData.amounts.map(amt => amt.div(amountsSum)); + } + for (const i of _.times(assetProxyData.amounts.length)) { + const nestedAssetData = assetProxyData.nestedAssetData[i]; + const assetAmountRatio = assetAmountRatios[i]; + await this.setBalanceAsync(userAddress, nestedAssetData, desiredBalance.times(assetAmountRatio)); + } + break; + } + default: + throw errorUtils.spawnSwitchErr('proxyId', proxyId); + } + } + public async setUnscaledBalanceAsync( + userAddress: string, + assetData: string, + desiredBalance: BigNumber, + ): Promise { + const proxyId = assetDataUtils.decodeAssetProxyId(assetData); + switch (proxyId) { + case AssetProxyId.ERC20: + case AssetProxyId.ERC721: + return this.setBalanceAsync(userAddress, assetData, desiredBalance); + case AssetProxyId.ERC1155: + case AssetProxyId.MultiAsset: { + const currentBalance = await this.getBalanceAsync(userAddress, assetData); + return this.setBalanceAsync(userAddress, assetData, desiredBalance.times(currentBalance)); + } default: throw errorUtils.spawnSwitchErr('proxyId', proxyId); } @@ -133,9 +256,32 @@ export class AssetWrapper { erc721ProxyData.tokenAddress, erc721ProxyData.tokenId, ); - const allowance = isProxyApproved ? new BigNumber(1) : new BigNumber(0); + const allowance = isProxyApproved ? ONE_NFT_UNIT : ZERO_NFT_UNIT; return allowance; } + case AssetProxyId.ERC1155: { + // tslint:disable-next-line:no-unnecessary-type-assertion + const assetProxyWrapper = this._proxyIdToAssetWrappers[proxyId] as ERC1155ProxyWrapper; + const assetProxyData = assetDataUtils.decodeERC1155AssetData(assetData); + const isApprovedForAll = await assetProxyWrapper.isProxyApprovedForAllAsync( + userAddress, + assetProxyData.tokenAddress, + ); + if (!isApprovedForAll) { + // ERC1155 is all or nothing. + return constants.ZERO_AMOUNT; + } + return constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; + } + case AssetProxyId.MultiAsset: { + const assetProxyData = assetDataUtils.decodeMultiAssetData(assetData); + const allowances = await Promise.all( + assetProxyData.nestedAssetData.map(async nestedAssetData => + this.getProxyAllowanceAsync(userAddress, nestedAssetData), + ), + ); + return BigNumber.min(...allowances); + } default: throw errorUtils.spawnSwitchErr('proxyId', proxyId); } @@ -209,9 +355,37 @@ export class AssetWrapper { (!isProxyApproved && desiredAllowance.eq(0)) || (isProxyApproved && desiredAllowance.eq(1)) ) { - return; // noop + // noop } - + break; + } + case AssetProxyId.ERC1155: { + // tslint:disable-next-line:no-unnecessary-type-assertion + const assetProxyWrapper = this._proxyIdToAssetWrappers[proxyId] as ERC1155ProxyWrapper; + const assetProxyData = assetDataUtils.decodeERC1155AssetData(assetData); + // ERC1155 allowances are all or nothing. + const shouldApprovedForAll = desiredAllowance.gt(0); + const currentAllowance = await this.getProxyAllowanceAsync(userAddress, assetData); + if (shouldApprovedForAll && currentAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) { + // Nothing to do. + } else if (!shouldApprovedForAll && currentAllowance.eq(constants.ZERO_AMOUNT)) { + // Nothing to do. + } else { + assetProxyWrapper.setProxyAllowanceForAllAsync( + userAddress, + assetProxyData.tokenAddress, + shouldApprovedForAll, + ); + } + break; + } + case AssetProxyId.MultiAsset: { + const assetProxyData = assetDataUtils.decodeMultiAssetData(assetData); + await Promise.all( + assetProxyData.nestedAssetData.map(async nestedAssetData => + this.setProxyAllowanceAsync(userAddress, nestedAssetData, desiredAllowance), + ), + ); break; } default: diff --git a/contracts/exchange/test/utils/fill_order_combinatorial_utils.ts b/contracts/exchange/test/utils/fill_order_combinatorial_utils.ts index 5bb5ca733a..a36914e7db 100644 --- a/contracts/exchange/test/utils/fill_order_combinatorial_utils.ts +++ b/contracts/exchange/test/utils/fill_order_combinatorial_utils.ts @@ -1,12 +1,13 @@ -import { ERC20Wrapper, ERC721Wrapper } from '@0x/contracts-asset-proxy'; -import { chaiSetup, constants, FillResults, orderUtils, signingUtils } from '@0x/contracts-test-utils'; import { - assetDataUtils, - BalanceAndProxyAllowanceLazyStore, - ExchangeRevertErrors, - orderHashUtils, -} from '@0x/order-utils'; -import { AssetProxyId, Order, SignatureType, SignedOrder } from '@0x/types'; + artifacts as assetProxyArtifacts, + ERC1155ProxyWrapper, + ERC20Wrapper, + ERC721Wrapper, + MultiAssetProxyContract, +} from '@0x/contracts-asset-proxy'; +import { chaiSetup, constants, FillResults, orderUtils, signingUtils } from '@0x/contracts-test-utils'; +import { BalanceAndProxyAllowanceLazyStore, ExchangeRevertErrors, orderHashUtils } from '@0x/order-utils'; +import { Order, SignatureType, SignedOrder } from '@0x/types'; import { BigNumber, errorUtils, providerUtils, RevertError, StringRevertError } from '@0x/utils'; import { SupportedProvider, Web3Wrapper } from '@0x/web3-wrapper'; import * as chai from 'chai'; @@ -30,12 +31,10 @@ import { OrderScenario, TakerAssetFillAmountScenario, TakerScenario, - TraderStateScenario, } from './fill_order_scenarios'; import { FillOrderError, FillOrderSimulator } from './fill_order_simulator'; import { OrderFactoryFromScenario } from './order_factory_from_scenario'; import { SimpleAssetBalanceAndProxyAllowanceFetcher } from './simple_asset_balance_and_proxy_allowance_fetcher'; -import { SimpleOrderFilledCancelledFetcher } from './simple_order_filled_cancelled_fetcher'; chaiSetup.configure(); const expect = chai.expect; @@ -66,7 +65,7 @@ export async function fillOrderCombinatorialUtilsFactoryAsync( ): Promise { const accounts = await web3Wrapper.getAvailableAddressesAsync(); const userAddresses = _.slice(accounts, 0, 5); - const [ownerAddress, makerAddress, takerAddress] = userAddresses; + const [ownerAddress, makerAddress, takerAddress, burnerAddress] = userAddresses; const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)]; const supportedProvider = web3Wrapper.getProvider(); @@ -74,6 +73,7 @@ export async function fillOrderCombinatorialUtilsFactoryAsync( const chainId = await providerUtils.getChainIdAsync(provider); const erc20Wrapper = new ERC20Wrapper(provider, userAddresses, ownerAddress); const erc721Wrapper = new ERC721Wrapper(provider, userAddresses, ownerAddress); + const erc1155Wrapper = new ERC1155ProxyWrapper(provider, userAddresses, ownerAddress); const erc20EighteenDecimalTokenCount = 4; const eighteenDecimals = new BigNumber(18); @@ -82,12 +82,12 @@ export async function fillOrderCombinatorialUtilsFactoryAsync( eighteenDecimals, ); - const erc20FiveDecimalTokenCount = 2; + const erc20FiveDecimalTokenCount = 4; const fiveDecimals = new BigNumber(5); const erc20FiveDecimalTokens = await erc20Wrapper.deployDummyTokensAsync(erc20FiveDecimalTokenCount, fiveDecimals); + const erc20ZeroDecimalTokenCount = 4; const zeroDecimals = new BigNumber(0); - const erc20ZeroDecimalTokenCount = 2; const erc20ZeroDecimalTokens = await erc20Wrapper.deployDummyTokensAsync(erc20ZeroDecimalTokenCount, zeroDecimals); const erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); @@ -97,7 +97,18 @@ export async function fillOrderCombinatorialUtilsFactoryAsync( await erc721Wrapper.setBalancesAndAllowancesAsync(); const erc721Balances = await erc721Wrapper.getBalancesAsync(); - const assetWrapper = new AssetWrapper([erc20Wrapper, erc721Wrapper]); + const [erc1155Token] = (await erc1155Wrapper.deployDummyContractsAsync()).map(w => w.getContract()); + const erc1155Proxy = await erc1155Wrapper.deployProxyAsync(); + await erc1155Wrapper.setBalancesAndAllowancesAsync(); + const erc1155Holdings = await erc1155Wrapper.getBalancesAsync(); + + const multiAssetProxy = await MultiAssetProxyContract.deployFrom0xArtifactAsync( + assetProxyArtifacts.MultiAssetProxy, + provider, + txDefaults, + ); + + const assetWrapper = new AssetWrapper([erc20Wrapper, erc721Wrapper, erc1155Wrapper], burnerAddress); const exchangeContract = await ExchangeContract.deployFrom0xArtifactAsync( artifacts.Exchange, @@ -108,13 +119,66 @@ export async function fillOrderCombinatorialUtilsFactoryAsync( const exchangeWrapper = new ExchangeWrapper(exchangeContract, provider); await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, ownerAddress); await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, ownerAddress); + await exchangeWrapper.registerAssetProxyAsync(erc1155Proxy.address, ownerAddress); + await exchangeWrapper.registerAssetProxyAsync(multiAssetProxy.address, ownerAddress); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeContract.address, { from: ownerAddress }), + await erc20Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync( + exchangeContract.address, + { from: ownerAddress }, constants.AWAIT_TRANSACTION_MINED_MS, ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeContract.address, { from: ownerAddress }), + + await erc721Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync( + exchangeContract.address, + { from: ownerAddress }, + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + await erc1155Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync( + exchangeContract.address, + { from: ownerAddress }, + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + await multiAssetProxy.addAuthorizedAddress.awaitTransactionSuccessAsync( + exchangeContract.address, + { from: ownerAddress }, + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + await erc20Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync( + multiAssetProxy.address, + { from: ownerAddress }, + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + await erc721Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync( + multiAssetProxy.address, + { from: ownerAddress }, + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + await erc1155Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync( + multiAssetProxy.address, + { from: ownerAddress }, + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + await multiAssetProxy.registerAssetProxy.awaitTransactionSuccessAsync( + erc20Proxy.address, + { from: ownerAddress }, + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + await multiAssetProxy.registerAssetProxy.awaitTransactionSuccessAsync( + erc721Proxy.address, + { from: ownerAddress }, + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + await multiAssetProxy.registerAssetProxy.awaitTransactionSuccessAsync( + erc1155Proxy.address, + { from: ownerAddress }, constants.AWAIT_TRANSACTION_MINED_MS, ); @@ -123,8 +187,10 @@ export async function fillOrderCombinatorialUtilsFactoryAsync( erc20EighteenDecimalTokens.map(token => token.address), erc20FiveDecimalTokens.map(token => token.address), erc20ZeroDecimalTokens.map(token => token.address), - erc721Token, + erc721Token.address, + erc1155Token.address, erc721Balances, + erc1155Holdings, exchangeContract.address, chainId, ); @@ -152,7 +218,6 @@ export class FillOrderCombinatorialUtils { public exchangeWrapper: ExchangeWrapper; public assetWrapper: AssetWrapper; public balanceAndProxyAllowanceFetcher: SimpleAssetBalanceAndProxyAllowanceFetcher; - public orderFilledCancelledFetcher: SimpleOrderFilledCancelledFetcher; public static generateFillOrderCombinations(): FillScenario[] { const takerScenarios = [ @@ -192,16 +257,25 @@ export class FillOrderCombinatorialUtils { AssetDataScenario.ERC20FiveDecimals, AssetDataScenario.ERC20EighteenDecimals, AssetDataScenario.ERC721, + AssetDataScenario.ERC1155Fungible, + AssetDataScenario.ERC1155NonFungible, + AssetDataScenario.MultiAssetERC20, ]; const takerAssetDataScenario = [ AssetDataScenario.ERC20FiveDecimals, AssetDataScenario.ERC20EighteenDecimals, AssetDataScenario.ERC721, + AssetDataScenario.ERC1155Fungible, + AssetDataScenario.ERC1155NonFungible, + AssetDataScenario.MultiAssetERC20, ]; const makerFeeAssetDataScenario = [ FeeAssetDataScenario.ERC20FiveDecimals, FeeAssetDataScenario.ERC20EighteenDecimals, FeeAssetDataScenario.ERC721, + FeeAssetDataScenario.ERC1155Fungible, + FeeAssetDataScenario.ERC1155NonFungible, + FeeAssetDataScenario.MultiAssetERC20, FeeAssetDataScenario.MakerToken, FeeAssetDataScenario.TakerToken, ]; @@ -209,6 +283,9 @@ export class FillOrderCombinatorialUtils { FeeAssetDataScenario.ERC20FiveDecimals, FeeAssetDataScenario.ERC20EighteenDecimals, FeeAssetDataScenario.ERC721, + FeeAssetDataScenario.ERC1155Fungible, + FeeAssetDataScenario.ERC1155NonFungible, + FeeAssetDataScenario.MultiAssetERC20, FeeAssetDataScenario.MakerToken, FeeAssetDataScenario.TakerToken, ]; @@ -374,7 +451,6 @@ export class FillOrderCombinatorialUtils { this.exchangeWrapper = exchangeWrapper; this.assetWrapper = assetWrapper; this.balanceAndProxyAllowanceFetcher = new SimpleAssetBalanceAndProxyAllowanceFetcher(assetWrapper); - this.orderFilledCancelledFetcher = new SimpleOrderFilledCancelledFetcher(exchangeWrapper); } public async testFillOrderScenarioAsync(fillScenario: FillScenario): Promise { @@ -399,17 +475,9 @@ export class FillOrderCombinatorialUtils { ): Promise { const lazyStore = new BalanceAndProxyAllowanceLazyStore(this.balanceAndProxyAllowanceFetcher); const signedOrder = await this._generateSignedOrder(fillScenario.orderScenario); - const takerAssetFillAmount = getTakerAssetFillAmountAsync( - signedOrder, - fillScenario.takerAssetFillAmountScenario, - ); + const takerAssetFillAmount = getTakerAssetFillAmount(signedOrder, fillScenario); - await this._modifyTraderStateAsync( - fillScenario.makerStateScenario, - fillScenario.takerStateScenario, - signedOrder, - takerAssetFillAmount, - ); + await this._modifyTraderStateAsync(fillScenario, signedOrder, takerAssetFillAmount); let expectedFillResults = EMPTY_FILL_RESULTS; let _fillErrorIfExists = fillErrorIfExists; @@ -473,26 +541,38 @@ export class FillOrderCombinatorialUtils { const takerFeeAssetData = signedOrder.takerAssetData; const feeRecipient = signedOrder.feeRecipientAddress; - const expMakerAssetBalanceOfMaker = await lazyStore.getBalanceAsync(makerAssetData, makerAddress); - const expMakerAssetAllowanceOfMaker = await lazyStore.getProxyAllowanceAsync(makerAssetData, makerAddress); - const expTakerAssetBalanceOfMaker = await lazyStore.getBalanceAsync(takerAssetData, makerAddress); - const expMakerFeeAssetBalanceOfMaker = await lazyStore.getBalanceAsync(makerFeeAssetData, makerAddress); - const expTakerFeeAssetBalanceOfMaker = await lazyStore.getBalanceAsync(takerFeeAssetData, makerAddress); - const expMakerFeeAssetAllowanceOfMaker = await lazyStore.getProxyAllowanceAsync( - makerFeeAssetData, - makerAddress, - ); - const expTakerAssetBalanceOfTaker = await lazyStore.getBalanceAsync(takerAssetData, this.takerAddress); - const expTakerAssetAllowanceOfTaker = await lazyStore.getProxyAllowanceAsync(takerAssetData, this.takerAddress); - const expMakerAssetBalanceOfTaker = await lazyStore.getBalanceAsync(makerAssetData, this.takerAddress); - const expMakerFeeAssetBalanceOfTaker = await lazyStore.getBalanceAsync(makerFeeAssetData, this.takerAddress); - const expTakerFeeAssetBalanceOfTaker = await lazyStore.getBalanceAsync(takerFeeAssetData, this.takerAddress); - const expTakerFeeAssetAllowanceOfTaker = await lazyStore.getProxyAllowanceAsync( - takerFeeAssetData, - this.takerAddress, - ); - const expMakerFeeAssetBalanceOfFeeRecipient = await lazyStore.getBalanceAsync(makerFeeAssetData, feeRecipient); - const expTakerFeeAssetBalanceOfFeeRecipient = await lazyStore.getBalanceAsync(takerFeeAssetData, feeRecipient); + const [ + expMakerAssetBalanceOfMaker, + expMakerAssetAllowanceOfMaker, + expTakerAssetBalanceOfMaker, + expMakerFeeAssetBalanceOfMaker, + expTakerFeeAssetBalanceOfMaker, + expMakerFeeAssetAllowanceOfMaker, + expTakerAssetBalanceOfTaker, + expTakerAssetAllowanceOfTaker, + expMakerAssetBalanceOfTaker, + expMakerFeeAssetBalanceOfTaker, + expTakerFeeAssetBalanceOfTaker, + expTakerFeeAssetAllowanceOfTaker, + expMakerFeeAssetBalanceOfFeeRecipient, + expTakerFeeAssetBalanceOfFeeRecipient, + ] = await Promise.all([ + lazyStore.getBalanceAsync(makerAssetData, makerAddress), + lazyStore.getProxyAllowanceAsync(makerAssetData, makerAddress), + lazyStore.getBalanceAsync(takerAssetData, makerAddress), + lazyStore.getBalanceAsync(makerFeeAssetData, makerAddress), + lazyStore.getBalanceAsync(takerFeeAssetData, makerAddress), + lazyStore.getProxyAllowanceAsync(makerFeeAssetData, makerAddress), + lazyStore.getBalanceAsync(takerAssetData, this.takerAddress), + lazyStore.getProxyAllowanceAsync(takerAssetData, this.takerAddress), + lazyStore.getBalanceAsync(makerAssetData, this.takerAddress), + lazyStore.getBalanceAsync(makerFeeAssetData, this.takerAddress), + lazyStore.getBalanceAsync(takerFeeAssetData, this.takerAddress), + lazyStore.getProxyAllowanceAsync(takerFeeAssetData, this.takerAddress), + lazyStore.getBalanceAsync(makerFeeAssetData, feeRecipient), + lazyStore.getBalanceAsync(takerFeeAssetData, feeRecipient), + ]); + const expFilledTakerAmount = expectedFillResults.takerAssetFilledAmount; const expFilledMakerAmount = expectedFillResults.makerAssetFilledAmount; const expMakerFeePaid = expectedFillResults.makerFeePaid; @@ -517,7 +597,40 @@ export class FillOrderCombinatorialUtils { }); const orderHash = orderHashUtils.getOrderHashHex(signedOrder); - const actFilledTakerAmount = await this.exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash); + const [ + actFilledTakerAmount, + actMakerAssetBalanceOfMaker, + actMakerAssetAllowanceOfMaker, + actTakerAssetBalanceOfMaker, + actMakerFeeAssetBalanceOfMaker, + actMakerFeeAssetAllowanceOfMaker, + actTakerFeeAssetBalanceOfMaker, + actTakerAssetBalanceOfTaker, + actTakerAssetAllowanceOfTaker, + actMakerAssetBalanceOfTaker, + actMakerFeeAssetBalanceOfTaker, + actTakerFeeAssetBalanceOfTaker, + actTakerFeeAssetAllowanceOfTaker, + actMakerFeeAssetBalanceOfFeeRecipient, + actTakerFeeAssetBalanceOfFeeRecipient, + ] = await Promise.all([ + this.exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash), + this.assetWrapper.getBalanceAsync(makerAddress, makerAssetData), + this.assetWrapper.getProxyAllowanceAsync(makerAddress, makerAssetData), + this.assetWrapper.getBalanceAsync(makerAddress, takerAssetData), + this.assetWrapper.getBalanceAsync(makerAddress, makerFeeAssetData), + this.assetWrapper.getProxyAllowanceAsync(makerAddress, makerFeeAssetData), + this.assetWrapper.getBalanceAsync(makerAddress, takerFeeAssetData), + this.assetWrapper.getBalanceAsync(this.takerAddress, takerAssetData), + this.assetWrapper.getProxyAllowanceAsync(this.takerAddress, takerAssetData), + this.assetWrapper.getBalanceAsync(this.takerAddress, makerAssetData), + this.assetWrapper.getBalanceAsync(this.takerAddress, makerFeeAssetData), + this.assetWrapper.getBalanceAsync(this.takerAddress, takerFeeAssetData), + this.assetWrapper.getProxyAllowanceAsync(this.takerAddress, takerFeeAssetData), + this.assetWrapper.getBalanceAsync(feeRecipient, makerFeeAssetData), + this.assetWrapper.getBalanceAsync(feeRecipient, takerFeeAssetData), + ]); + expect(actFilledTakerAmount, 'filledTakerAmount').to.be.bignumber.equal(expFilledTakerAmount); const exchangeLogs = _.filter( @@ -542,105 +655,52 @@ export class FillOrderCombinatorialUtils { expect(log.args.makerAssetData, 'log.args.makerAssetData').to.be.equal(makerAssetData); expect(log.args.takerAssetData, 'log.args.takerAssetData').to.be.equal(takerAssetData); - const actMakerAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, makerAssetData); expect(actMakerAssetBalanceOfMaker, 'makerAssetBalanceOfMaker').to.be.bignumber.equal( expMakerAssetBalanceOfMaker, ); - - const actMakerAssetAllowanceOfMaker = await this.assetWrapper.getProxyAllowanceAsync( - makerAddress, - makerAssetData, - ); expect(actMakerAssetAllowanceOfMaker, 'makerAssetAllowanceOfMaker').to.be.bignumber.equal( expMakerAssetAllowanceOfMaker, ); - - const actTakerAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, takerAssetData); expect(actTakerAssetBalanceOfMaker, 'takerAssetBalanceOfMaker').to.be.bignumber.equal( expTakerAssetBalanceOfMaker, ); - - const actMakerFeeAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, makerFeeAssetData); expect(actMakerFeeAssetBalanceOfMaker, 'makerFeeAssetBalanceOfMaker').to.be.bignumber.equal( expMakerFeeAssetBalanceOfMaker, ); - - const actMakerFeeAssetAllowanceOfMaker = await this.assetWrapper.getProxyAllowanceAsync( - makerAddress, - makerFeeAssetData, - ); expect(actMakerFeeAssetAllowanceOfMaker, 'makerFeeAssetAllowanceOfMaker').to.be.bignumber.equal( expMakerFeeAssetAllowanceOfMaker, ); - - const actTakerFeeAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, takerFeeAssetData); expect(actTakerFeeAssetBalanceOfMaker, 'takerFeeAssetBalanceOfMaker').to.be.bignumber.equal( expTakerFeeAssetBalanceOfMaker, ); - - const actTakerAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, takerAssetData); expect(actTakerAssetBalanceOfTaker, 'TakerAssetBalanceOfTaker').to.be.bignumber.equal( expTakerAssetBalanceOfTaker, ); - - const actTakerAssetAllowanceOfTaker = await this.assetWrapper.getProxyAllowanceAsync( - this.takerAddress, - takerAssetData, - ); - expect(actTakerAssetAllowanceOfTaker, 'takerAssetAllowanceOfTaker').to.be.bignumber.equal( expTakerAssetAllowanceOfTaker, ); - - const actMakerAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, makerAssetData); expect(actMakerAssetBalanceOfTaker, 'makerAssetBalanceOfTaker').to.be.bignumber.equal( expMakerAssetBalanceOfTaker, ); - - const actMakerFeeAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync( - this.takerAddress, - makerFeeAssetData, - ); expect(actMakerFeeAssetBalanceOfTaker, 'makerFeeAssetBalanceOfTaker').to.be.bignumber.equal( expMakerFeeAssetBalanceOfTaker, ); - - const actTakerFeeAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync( - this.takerAddress, - takerFeeAssetData, - ); expect(actTakerFeeAssetBalanceOfTaker, 'takerFeeAssetBalanceOfTaker').to.be.bignumber.equal( expTakerFeeAssetBalanceOfTaker, ); - - const actTakerFeeAssetAllowanceOfTaker = await this.assetWrapper.getProxyAllowanceAsync( - this.takerAddress, - takerFeeAssetData, - ); expect(actTakerFeeAssetAllowanceOfTaker, 'takerFeeAssetAllowanceOfTaker').to.be.bignumber.equal( expTakerFeeAssetAllowanceOfTaker, ); - - const actMakerFeeAssetBalanceOfFeeRecipient = await this.assetWrapper.getBalanceAsync( - feeRecipient, - makerFeeAssetData, - ); expect(actMakerFeeAssetBalanceOfFeeRecipient, 'makerFeeAssetBalanceOfFeeRecipient').to.be.bignumber.equal( expMakerFeeAssetBalanceOfFeeRecipient, ); - - const actTakerFeeAssetBalanceOfFeeRecipient = await this.assetWrapper.getBalanceAsync( - feeRecipient, - takerFeeAssetData, - ); expect(actTakerFeeAssetBalanceOfFeeRecipient, 'takerFeeAssetBalanceOfFeeRecipient').to.be.bignumber.equal( expTakerFeeAssetBalanceOfFeeRecipient, ); } private async _modifyTraderStateAsync( - makerStateScenario: TraderStateScenario, - takerStateScenario: TraderStateScenario, + fillScenario: FillScenario, signedOrder: SignedOrder, takerAssetFillAmount: BigNumber, ): Promise { @@ -663,7 +723,7 @@ export class FillOrderCombinatorialUtils { ); let makerAssetBalance; - switch (makerStateScenario.traderAssetBalance) { + switch (fillScenario.makerStateScenario.traderAssetBalance) { case BalanceAmountScenario.Higher: break; // Noop since this is already the default @@ -685,11 +745,11 @@ export class FillOrderCombinatorialUtils { default: throw errorUtils.spawnSwitchErr( 'makerStateScenario.traderAssetBalance', - makerStateScenario.traderAssetBalance, + fillScenario.makerStateScenario.traderAssetBalance, ); } if (makerAssetBalance !== undefined) { - await this.assetWrapper.setBalanceAsync( + await this.assetWrapper.setUnscaledBalanceAsync( signedOrder.makerAddress, signedOrder.makerAssetData, makerAssetBalance, @@ -697,7 +757,7 @@ export class FillOrderCombinatorialUtils { } let takerAssetBalance; - switch (takerStateScenario.traderAssetBalance) { + switch (fillScenario.takerStateScenario.traderAssetBalance) { case BalanceAmountScenario.Higher: break; // Noop since this is already the default @@ -719,15 +779,21 @@ export class FillOrderCombinatorialUtils { default: throw errorUtils.spawnSwitchErr( 'takerStateScenario.traderAssetBalance', - takerStateScenario.traderAssetBalance, + fillScenario.takerStateScenario.traderAssetBalance, ); } if (takerAssetBalance !== undefined) { - await this.assetWrapper.setBalanceAsync(this.takerAddress, signedOrder.takerAssetData, takerAssetBalance); + await this.assetWrapper.setUnscaledBalanceAsync( + this.takerAddress, + signedOrder.takerAssetData, + takerAssetBalance, + ); } + const isMakerFeeAssetMakerAsset = + fillScenario.orderScenario.makerFeeAssetDataScenario === FeeAssetDataScenario.MakerToken; let makerFeeBalance; - switch (makerStateScenario.feeBalance) { + switch (fillScenario.makerStateScenario.feeBalance) { case BalanceAmountScenario.Higher: break; // Noop since this is already the default @@ -747,18 +813,23 @@ export class FillOrderCombinatorialUtils { break; default: - throw errorUtils.spawnSwitchErr('makerStateScenario.feeBalance', makerStateScenario.feeBalance); + throw errorUtils.spawnSwitchErr( + 'makerStateScenario.feeBalance', + fillScenario.makerStateScenario.feeBalance, + ); } - if (makerFeeBalance !== undefined) { - await this.assetWrapper.setBalanceAsync( + if (isMakerFeeAssetMakerAsset && makerFeeBalance !== undefined) { + await this.assetWrapper.setUnscaledBalanceAsync( signedOrder.makerAddress, signedOrder.makerFeeAssetData, makerFeeBalance, ); } + const isTakerFeeAssetTakerAsset = + fillScenario.orderScenario.takerFeeAssetDataScenario === FeeAssetDataScenario.TakerToken; let takerFeeBalance; - switch (takerStateScenario.feeBalance) { + switch (fillScenario.takerStateScenario.feeBalance) { case BalanceAmountScenario.Higher: break; // Noop since this is already the default @@ -778,14 +849,21 @@ export class FillOrderCombinatorialUtils { break; default: - throw errorUtils.spawnSwitchErr('takerStateScenario.feeBalance', takerStateScenario.feeBalance); + throw errorUtils.spawnSwitchErr( + 'takerStateScenario.feeBalance', + fillScenario.takerStateScenario.feeBalance, + ); } - if (takerFeeBalance !== undefined) { - await this.assetWrapper.setBalanceAsync(this.takerAddress, signedOrder.takerFeeAssetData, takerFeeBalance); + if (isTakerFeeAssetTakerAsset && takerFeeBalance !== undefined) { + await this.assetWrapper.setUnscaledBalanceAsync( + this.takerAddress, + signedOrder.takerFeeAssetData, + takerFeeBalance, + ); } let makerAssetAllowance; - switch (makerStateScenario.traderAssetAllowance) { + switch (fillScenario.makerStateScenario.traderAssetAllowance) { case AllowanceAmountScenario.Higher: break; // Noop since this is already the default @@ -808,7 +886,7 @@ export class FillOrderCombinatorialUtils { default: throw errorUtils.spawnSwitchErr( 'makerStateScenario.traderAssetAllowance', - makerStateScenario.traderAssetAllowance, + fillScenario.makerStateScenario.traderAssetAllowance, ); } if (makerAssetAllowance !== undefined) { @@ -820,7 +898,7 @@ export class FillOrderCombinatorialUtils { } let takerAssetAllowance; - switch (takerStateScenario.traderAssetAllowance) { + switch (fillScenario.takerStateScenario.traderAssetAllowance) { case AllowanceAmountScenario.Higher: break; // Noop since this is already the default @@ -843,7 +921,7 @@ export class FillOrderCombinatorialUtils { default: throw errorUtils.spawnSwitchErr( 'takerStateScenario.traderAssetAllowance', - takerStateScenario.traderAssetAllowance, + fillScenario.takerStateScenario.traderAssetAllowance, ); } if (takerAssetAllowance !== undefined) { @@ -855,7 +933,7 @@ export class FillOrderCombinatorialUtils { } let makerFeeAllowance; - switch (makerStateScenario.feeAllowance) { + switch (fillScenario.makerStateScenario.feeAllowance) { case AllowanceAmountScenario.Higher: break; // Noop since this is already the default @@ -876,9 +954,12 @@ export class FillOrderCombinatorialUtils { break; default: - throw errorUtils.spawnSwitchErr('makerStateScenario.feeAllowance', makerStateScenario.feeAllowance); + throw errorUtils.spawnSwitchErr( + 'makerStateScenario.feeAllowance', + fillScenario.makerStateScenario.feeAllowance, + ); } - if (makerFeeAllowance !== undefined) { + if (isMakerFeeAssetMakerAsset && makerFeeAllowance !== undefined) { await this.assetWrapper.setProxyAllowanceAsync( signedOrder.makerAddress, signedOrder.makerFeeAssetData, @@ -887,7 +968,7 @@ export class FillOrderCombinatorialUtils { } let takerFeeAllowance; - switch (takerStateScenario.feeAllowance) { + switch (fillScenario.takerStateScenario.feeAllowance) { case AllowanceAmountScenario.Higher: break; // Noop since this is already the default @@ -908,9 +989,12 @@ export class FillOrderCombinatorialUtils { break; default: - throw errorUtils.spawnSwitchErr('takerStateScenario.feeAllowance', takerStateScenario.feeAllowance); + throw errorUtils.spawnSwitchErr( + 'takerStateScenario.feeAllowance', + fillScenario.takerStateScenario.feeAllowance, + ); } - if (takerFeeAllowance !== undefined) { + if (isTakerFeeAssetTakerAsset && takerFeeAllowance !== undefined) { await this.assetWrapper.setProxyAllowanceAsync( this.takerAddress, signedOrder.takerFeeAssetData, @@ -920,12 +1004,9 @@ export class FillOrderCombinatorialUtils { } } -function getTakerAssetFillAmountAsync( - signedOrder: SignedOrder, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario, -): BigNumber { +function getTakerAssetFillAmount(signedOrder: SignedOrder, fillScenario: FillScenario): BigNumber { let takerAssetFillAmount; - switch (takerAssetFillAmountScenario) { + switch (fillScenario.takerAssetFillAmountScenario) { case TakerAssetFillAmountScenario.Zero: takerAssetFillAmount = new BigNumber(0); break; @@ -939,20 +1020,42 @@ function getTakerAssetFillAmountAsync( break; case TakerAssetFillAmountScenario.LessThanTakerAssetAmount: - const takerAssetProxyId = assetDataUtils.decodeAssetProxyId(signedOrder.takerAssetData); - const makerAssetProxyId = assetDataUtils.decodeAssetProxyId(signedOrder.makerAssetData); - const isEitherAssetERC721 = - takerAssetProxyId === AssetProxyId.ERC721 || makerAssetProxyId === AssetProxyId.ERC721; - if (isEitherAssetERC721) { - throw new Error( - 'Cannot test `TakerAssetFillAmountScenario.LessThanTakerAssetAmount` together with ERC721 assets since orders involving ERC721 must always be filled exactly.', - ); - } + // TODO(dorothy-zbornak): Do we need this? + // const { + // makerAssetDataScenario, + // takerAssetDataScenario, + // makerFeeAssetDataScenario, + // takerFeeAssetDataScenario, + // } = fillScenario.orderScenario; + // const NFT_SCENARIOS = [ + // AssetDataScenario.ERC721, + // AssetDataScenario.ERC1155NonFungible, + // ]; + // const FEE_NFT_SCENARIOS = [ + // FeeAssetDataScenario.ERC721, + // FeeAssetDataScenario.ERC1155NonFungible, + // ]; + // if (_.includes(NFT_SCENARIOS, makerAssetDataScenario)) { + // FEE_NFT_SCENARIOS.push(FeeAssetDataScenario.MakerToken); + // } + // if (_.includes(NFT_SCENARIOS, takerAssetDataScenario)) { + // FEE_NFT_SCENARIOS.push(FeeAssetDataScenario.TakerToken); + // } + // const isAnyAssetNonFungible = + // _.includes(NFT_SCENARIOS, makerAssetDataScenario) || + // _.includes(NFT_SCENARIOS, takerAssetDataScenario) || + // _.includes(FEE_NFT_SCENARIOS, makerFeeAssetDataScenario) || + // _.includes(FEE_NFT_SCENARIOS, takerFeeAssetDataScenario); + // if (isAnyAssetNonFungible) { + // throw new Error( + // 'Cannot test `TakerAssetFillAmountScenario.LessThanTakerAssetAmount` together with ERC721 assets since orders involving ERC721 must always be filled exactly.', + // ); + // } takerAssetFillAmount = signedOrder.takerAssetAmount.div(2).integerValue(BigNumber.ROUND_FLOOR); break; default: - throw errorUtils.spawnSwitchErr('TakerAssetFillAmountScenario', takerAssetFillAmountScenario); + throw errorUtils.spawnSwitchErr('TakerAssetFillAmountScenario', fillScenario.takerAssetFillAmountScenario); } return takerAssetFillAmount; @@ -971,11 +1074,7 @@ function fillErrorToRevertError(order: Order, error: FillOrderError): RevertErro case FillOrderError.InvalidFillPrice: return new ExchangeRevertErrors.FillError(ExchangeRevertErrors.FillErrorCode.InvalidFillPrice, orderHash); case FillOrderError.TransferFailed: - return new ExchangeRevertErrors.AssetProxyTransferError( - orderHash, - undefined, - new StringRevertError(FillOrderError.TransferFailed).encode(), - ); + return new ExchangeRevertErrors.AssetProxyTransferError(orderHash); default: return new StringRevertError(error); } diff --git a/contracts/exchange/test/utils/fill_order_scenarios.ts b/contracts/exchange/test/utils/fill_order_scenarios.ts index 1ae8474cec..99deb70a49 100644 --- a/contracts/exchange/test/utils/fill_order_scenarios.ts +++ b/contracts/exchange/test/utils/fill_order_scenarios.ts @@ -27,6 +27,9 @@ export enum AssetDataScenario { ERC20FiveDecimals = 'ERC20_FIVE_DECIMALS', ERC20EighteenDecimals = 'ERC20_EIGHTEEN_DECIMALS', ERC721 = 'ERC721', + ERC1155Fungible = 'ERC1155_FUNGIBLE', + ERC1155NonFungible = 'ERC1155_NON_FUNGIBLE', + MultiAssetERC20 = 'MULTI_ASSET_ERC20', } export enum FeeAssetDataScenario { @@ -34,6 +37,9 @@ export enum FeeAssetDataScenario { ERC20FiveDecimals = 'ERC20_FIVE_DECIMALS', ERC20EighteenDecimals = 'ERC20_EIGHTEEN_DECIMALS', ERC721 = 'ERC721', + ERC1155Fungible = 'ERC1155_FUNGIBLE', + ERC1155NonFungible = 'ERC1155_NON_FUNGIBLE', + MultiAssetERC20 = 'MULTI_ASSET_ERC20', MakerToken = 'MAKER_TOKEN', TakerToken = 'TAKER_TOKEN', } diff --git a/contracts/exchange/test/utils/order_factory_from_scenario.ts b/contracts/exchange/test/utils/order_factory_from_scenario.ts index 2e73205794..50924bf0fd 100644 --- a/contracts/exchange/test/utils/order_factory_from_scenario.ts +++ b/contracts/exchange/test/utils/order_factory_from_scenario.ts @@ -1,8 +1,8 @@ -import { DummyERC721TokenContract } from '@0x/contracts-erc721'; -import { constants, ERC721TokenIdsByOwner } from '@0x/contracts-test-utils'; +import { constants, ERC1155HoldingsByOwner, ERC721TokenIdsByOwner } from '@0x/contracts-test-utils'; import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils'; import { Order } from '@0x/types'; import { BigNumber, errorUtils } from '@0x/utils'; +import * as _ from 'lodash'; import { AssetDataScenario, @@ -17,13 +17,16 @@ import { const TEN_UNITS_EIGHTEEN_DECIMALS = new BigNumber('10e18'); const FIVE_UNITS_EIGHTEEN_DECIMALS = new BigNumber('5e18'); const POINT_ONE_UNITS_EIGHTEEN_DECIMALS = new BigNumber('0.1e18'); +const ONE_UNITS_EIGHTEEN_DECIMALS = new BigNumber('1e18'); const POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS = new BigNumber('0.05e18'); const TEN_UNITS_FIVE_DECIMALS = new BigNumber('10e5'); const FIVE_UNITS_FIVE_DECIMALS = new BigNumber('5e5'); const POINT_ONE_UNITS_FIVE_DECIMALS = new BigNumber('0.1e5'); +const ONE_UNITS_FIVE_DECIMALS = new BigNumber('1e5'); const POINT_ZERO_FIVE_UNITS_FIVE_DECIMALS = new BigNumber('0.05e5'); -const TEN_UNITS_ZERO_DECIMALS = new BigNumber(10); const ONE_THOUSAND_UNITS_ZERO_DECIMALS = new BigNumber(1000); +const TEN_UNITS_ZERO_DECIMALS = new BigNumber(10); +const FIVE_UNITS_ZERO_DECIMALS = new BigNumber(5); const ONE_UNITS_ZERO_DECIMALS = new BigNumber(1); const ONE_NFT_UNIT = new BigNumber(1); const ZERO_UNITS = new BigNumber(0); @@ -33,8 +36,10 @@ export class OrderFactoryFromScenario { private readonly _erc20EighteenDecimalTokenAddresses: string[]; private readonly _erc20FiveDecimalTokenAddresses: string[]; private readonly _erc20ZeroDecimalTokenAddresses: string[]; - private readonly _erc721Token: DummyERC721TokenContract; + private readonly _erc721TokenAddress: string; + private readonly _erc1155TokenAddress: string; private readonly _erc721Balances: ERC721TokenIdsByOwner; + private readonly _erc1155Holdings: ERC1155HoldingsByOwner; private readonly _exchangeAddress: string; private readonly _chainId: number; constructor( @@ -42,8 +47,10 @@ export class OrderFactoryFromScenario { erc20EighteenDecimalTokenAddresses: string[], erc20FiveDecimalTokenAddresses: string[], erc20ZeroDecimalTokenAddresses: string[], - erc721Token: DummyERC721TokenContract, + erc721TokenAddress: string, + erc1155TokenAddress: string, erc721Balances: ERC721TokenIdsByOwner, + erc1155Holdings: ERC1155HoldingsByOwner, exchangeAddress: string, chainId: number, ) { @@ -51,16 +58,31 @@ export class OrderFactoryFromScenario { this._erc20EighteenDecimalTokenAddresses = erc20EighteenDecimalTokenAddresses; this._erc20FiveDecimalTokenAddresses = erc20FiveDecimalTokenAddresses; this._erc20ZeroDecimalTokenAddresses = erc20ZeroDecimalTokenAddresses; - this._erc721Token = erc721Token; + this._erc721TokenAddress = erc721TokenAddress; + this._erc1155TokenAddress = erc1155TokenAddress; this._erc721Balances = erc721Balances; + this._erc1155Holdings = erc1155Holdings; this._exchangeAddress = exchangeAddress; this._chainId = chainId; } public generateOrder(orderScenario: OrderScenario): Order { const makerAddress = this._userAddresses[1]; let takerAddress = this._userAddresses[2]; - const erc721MakerAssetIds = this._erc721Balances[makerAddress][this._erc721Token.address]; - const erc721TakerAssetIds = this._erc721Balances[takerAddress][this._erc721Token.address]; + const erc721MakerAssetIds = this._erc721Balances[makerAddress][this._erc721TokenAddress]; + const erc721TakerAssetIds = this._erc721Balances[takerAddress][this._erc721TokenAddress]; + const erc1155FungibleMakerTokenIds = getERC1155FungibleOwnerTokenIds( + this._erc1155Holdings.fungible[makerAddress][this._erc1155TokenAddress], + ); + const erc1155NonFungibleMakerTokenIds = getERC1155NonFungibleOwnerTokenIds( + this._erc1155Holdings.nonFungible[makerAddress][this._erc1155TokenAddress], + ); + const erc1155FungibleTakerTokenIds = getERC1155FungibleOwnerTokenIds( + this._erc1155Holdings.fungible[takerAddress][this._erc1155TokenAddress], + ); + const erc1155NonFungibleTakerTokenIds = getERC1155NonFungibleOwnerTokenIds( + this._erc1155Holdings.nonFungible[takerAddress][this._erc1155TokenAddress], + ); + const erc1155CallbackData = constants.NULL_BYTES; let feeRecipientAddress; let makerAssetAmount; let takerAssetAmount; @@ -91,14 +113,36 @@ export class OrderFactoryFromScenario { makerAssetData = assetDataUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[0]); break; case AssetDataScenario.ERC721: - makerAssetData = assetDataUtils.encodeERC721AssetData( - this._erc721Token.address, - erc721MakerAssetIds[0], - ); + makerAssetData = assetDataUtils.encodeERC721AssetData(this._erc721TokenAddress, erc721MakerAssetIds[0]); break; case AssetDataScenario.ERC20ZeroDecimals: makerAssetData = assetDataUtils.encodeERC20AssetData(this._erc20ZeroDecimalTokenAddresses[0]); break; + case AssetDataScenario.ERC1155Fungible: + makerAssetData = assetDataUtils.encodeERC1155AssetData( + this._erc1155TokenAddress, + [erc1155FungibleMakerTokenIds[0]], + [ONE_UNITS_ZERO_DECIMALS], + erc1155CallbackData, + ); + break; + case AssetDataScenario.ERC1155NonFungible: + makerAssetData = assetDataUtils.encodeERC1155AssetData( + this._erc1155TokenAddress, + [erc1155NonFungibleMakerTokenIds[0]], + [ONE_UNITS_ZERO_DECIMALS], + erc1155CallbackData, + ); + break; + case AssetDataScenario.MultiAssetERC20: + makerAssetData = assetDataUtils.encodeMultiAssetData( + [ONE_UNITS_EIGHTEEN_DECIMALS, ONE_UNITS_FIVE_DECIMALS], + [ + assetDataUtils.encodeERC20AssetData(this._erc20EighteenDecimalTokenAddresses[0]), + assetDataUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[0]), + ], + ); + break; default: throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario); } @@ -111,14 +155,36 @@ export class OrderFactoryFromScenario { takerAssetData = assetDataUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[1]); break; case AssetDataScenario.ERC721: - takerAssetData = assetDataUtils.encodeERC721AssetData( - this._erc721Token.address, - erc721TakerAssetIds[0], - ); + takerAssetData = assetDataUtils.encodeERC721AssetData(this._erc721TokenAddress, erc721TakerAssetIds[0]); break; case AssetDataScenario.ERC20ZeroDecimals: takerAssetData = assetDataUtils.encodeERC20AssetData(this._erc20ZeroDecimalTokenAddresses[1]); break; + case AssetDataScenario.ERC1155Fungible: + takerAssetData = assetDataUtils.encodeERC1155AssetData( + this._erc1155TokenAddress, + [erc1155FungibleTakerTokenIds[1]], + [ONE_UNITS_ZERO_DECIMALS], + erc1155CallbackData, + ); + break; + case AssetDataScenario.ERC1155NonFungible: + takerAssetData = assetDataUtils.encodeERC1155AssetData( + this._erc1155TokenAddress, + [erc1155NonFungibleTakerTokenIds[0]], + [ONE_UNITS_ZERO_DECIMALS], + erc1155CallbackData, + ); + break; + case AssetDataScenario.MultiAssetERC20: + takerAssetData = assetDataUtils.encodeMultiAssetData( + [ONE_UNITS_EIGHTEEN_DECIMALS, ONE_UNITS_FIVE_DECIMALS], + [ + assetDataUtils.encodeERC20AssetData(this._erc20EighteenDecimalTokenAddresses[1]), + assetDataUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[1]), + ], + ); + break; default: throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario); } @@ -127,17 +193,22 @@ export class OrderFactoryFromScenario { case OrderAssetAmountScenario.Large: switch (orderScenario.makerAssetDataScenario) { case AssetDataScenario.ERC20EighteenDecimals: + case AssetDataScenario.ERC1155Fungible: makerAssetAmount = TEN_UNITS_EIGHTEEN_DECIMALS; break; case AssetDataScenario.ERC20FiveDecimals: makerAssetAmount = TEN_UNITS_FIVE_DECIMALS; break; case AssetDataScenario.ERC721: + case AssetDataScenario.ERC1155NonFungible: makerAssetAmount = ONE_NFT_UNIT; break; case AssetDataScenario.ERC20ZeroDecimals: makerAssetAmount = ONE_THOUSAND_UNITS_ZERO_DECIMALS; break; + case AssetDataScenario.MultiAssetERC20: + makerAssetAmount = TEN_UNITS_ZERO_DECIMALS; + break; default: throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario); } @@ -145,17 +216,22 @@ export class OrderFactoryFromScenario { case OrderAssetAmountScenario.Small: switch (orderScenario.makerAssetDataScenario) { case AssetDataScenario.ERC20EighteenDecimals: + case AssetDataScenario.ERC1155Fungible: makerAssetAmount = FIVE_UNITS_EIGHTEEN_DECIMALS; break; case AssetDataScenario.ERC20FiveDecimals: makerAssetAmount = FIVE_UNITS_FIVE_DECIMALS; break; case AssetDataScenario.ERC721: + case AssetDataScenario.ERC1155NonFungible: makerAssetAmount = ONE_NFT_UNIT; break; case AssetDataScenario.ERC20ZeroDecimals: makerAssetAmount = TEN_UNITS_ZERO_DECIMALS; break; + case AssetDataScenario.MultiAssetERC20: + makerAssetAmount = ONE_UNITS_ZERO_DECIMALS; + break; default: throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario); } @@ -171,17 +247,22 @@ export class OrderFactoryFromScenario { case OrderAssetAmountScenario.Large: switch (orderScenario.takerAssetDataScenario) { case AssetDataScenario.ERC20EighteenDecimals: + case AssetDataScenario.ERC1155Fungible: takerAssetAmount = TEN_UNITS_EIGHTEEN_DECIMALS; break; case AssetDataScenario.ERC20FiveDecimals: takerAssetAmount = TEN_UNITS_FIVE_DECIMALS; break; case AssetDataScenario.ERC721: + case AssetDataScenario.ERC1155NonFungible: takerAssetAmount = ONE_NFT_UNIT; break; case AssetDataScenario.ERC20ZeroDecimals: takerAssetAmount = ONE_THOUSAND_UNITS_ZERO_DECIMALS; break; + case AssetDataScenario.MultiAssetERC20: + takerAssetAmount = TEN_UNITS_ZERO_DECIMALS; + break; default: throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario); } @@ -189,17 +270,22 @@ export class OrderFactoryFromScenario { case OrderAssetAmountScenario.Small: switch (orderScenario.takerAssetDataScenario) { case AssetDataScenario.ERC20EighteenDecimals: + case AssetDataScenario.ERC1155Fungible: takerAssetAmount = FIVE_UNITS_EIGHTEEN_DECIMALS; break; case AssetDataScenario.ERC20FiveDecimals: takerAssetAmount = FIVE_UNITS_FIVE_DECIMALS; break; case AssetDataScenario.ERC721: + case AssetDataScenario.ERC1155NonFungible: takerAssetAmount = ONE_NFT_UNIT; break; case AssetDataScenario.ERC20ZeroDecimals: takerAssetAmount = TEN_UNITS_ZERO_DECIMALS; break; + case AssetDataScenario.MultiAssetERC20: + takerAssetAmount = ONE_UNITS_ZERO_DECIMALS; + break; default: throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario); } @@ -218,6 +304,8 @@ export class OrderFactoryFromScenario { erc20FiveDecimalTokenAddress: string, erc20ZeroDecimalTokenAddress: string, erc721AssetId: BigNumber, + erc1155FungibleTokenId: BigNumber, + erc1155NonFungibleAssetId: BigNumber, ): [BigNumber, string] => { const feeAmount = getFeeAmountFromScenario(orderScenario, feeAssetDataScenario, feeAmountScenario); switch (feeAssetDataScenario) { @@ -232,7 +320,38 @@ export class OrderFactoryFromScenario { case FeeAssetDataScenario.ERC20ZeroDecimals: return [feeAmount, assetDataUtils.encodeERC20AssetData(erc20ZeroDecimalTokenAddress)]; case FeeAssetDataScenario.ERC721: - return [feeAmount, assetDataUtils.encodeERC721AssetData(this._erc721Token.address, erc721AssetId)]; + return [feeAmount, assetDataUtils.encodeERC721AssetData(this._erc721TokenAddress, erc721AssetId)]; + case FeeAssetDataScenario.ERC1155Fungible: + return [ + feeAmount, + assetDataUtils.encodeERC1155AssetData( + this._erc1155TokenAddress, + [erc1155FungibleTokenId], + [ONE_UNITS_ZERO_DECIMALS], + erc1155CallbackData, + ), + ]; + case FeeAssetDataScenario.ERC1155NonFungible: + return [ + feeAmount, + assetDataUtils.encodeERC1155AssetData( + this._erc1155TokenAddress, + [erc1155NonFungibleAssetId], + [ONE_UNITS_ZERO_DECIMALS], + erc1155CallbackData, + ), + ]; + case FeeAssetDataScenario.MultiAssetERC20: + return [ + feeAmount, + assetDataUtils.encodeMultiAssetData( + [POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS, POINT_ZERO_FIVE_UNITS_FIVE_DECIMALS], + [ + assetDataUtils.encodeERC20AssetData(erc20EighteenDecimalTokenAddress), + assetDataUtils.encodeERC20AssetData(erc20FiveDecimalTokenAddress), + ], + ), + ]; default: throw errorUtils.spawnSwitchErr('FeeAssetDataScenario', feeAssetDataScenario); } @@ -245,6 +364,8 @@ export class OrderFactoryFromScenario { this._erc20FiveDecimalTokenAddresses[2], this._erc20ZeroDecimalTokenAddresses[2], erc721MakerAssetIds[1], + erc1155FungibleMakerTokenIds[2], + erc1155NonFungibleMakerTokenIds[1], ); [takerFee, takerFeeAssetData] = feeFromScenario( orderScenario.takerFeeScenario, @@ -253,6 +374,8 @@ export class OrderFactoryFromScenario { this._erc20FiveDecimalTokenAddresses[3], this._erc20ZeroDecimalTokenAddresses[3], erc721TakerAssetIds[1], + erc1155FungibleTakerTokenIds[3], + erc1155NonFungibleTakerTokenIds[1], ); switch (orderScenario.expirationTimeSecondsScenario) { @@ -318,6 +441,7 @@ function getFeeAmountFromScenario( ): BigNumber { switch (feeAssetDataScenario) { case FeeAssetDataScenario.ERC721: + case FeeAssetDataScenario.ERC1155NonFungible: switch (feeAmountScenario) { case OrderAssetAmountScenario.Zero: return ZERO_UNITS; @@ -349,6 +473,7 @@ function getFeeAmountFromScenario( throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', feeAmountScenario); } case FeeAssetDataScenario.ERC20EighteenDecimals: + case FeeAssetDataScenario.ERC1155Fungible: switch (feeAmountScenario) { case OrderAssetAmountScenario.Zero: return ZERO_UNITS; @@ -359,6 +484,17 @@ function getFeeAmountFromScenario( default: throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', feeAmountScenario); } + case FeeAssetDataScenario.MultiAssetERC20: + switch (feeAmountScenario) { + case OrderAssetAmountScenario.Zero: + return ZERO_UNITS; + case OrderAssetAmountScenario.Small: + return ONE_UNITS_ZERO_DECIMALS; + case OrderAssetAmountScenario.Large: + return FIVE_UNITS_ZERO_DECIMALS; + default: + throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', feeAmountScenario); + } case FeeAssetDataScenario.MakerToken: return getFeeAmountFromScenario(orderScenario, orderScenario.makerAssetDataScenario, feeAmountScenario); case FeeAssetDataScenario.TakerToken: @@ -367,3 +503,13 @@ function getFeeAmountFromScenario( throw errorUtils.spawnSwitchErr('FeeAssetDataScenario', feeAssetDataScenario); } } + +function getERC1155FungibleOwnerTokenIds(holdings: { [tokenId: string]: BigNumber }): BigNumber[] { + return _.keys(holdings).map(id => new BigNumber(id)); +} + +function getERC1155NonFungibleOwnerTokenIds(holdings: { [tokenId: string]: BigNumber[] }): BigNumber[] { + return _.values(holdings).map(group => group[0]); +} + +// tslint:disable: max-file-line-count diff --git a/contracts/exchange/test/utils/simple_order_filled_cancelled_fetcher.ts b/contracts/exchange/test/utils/simple_order_filled_cancelled_fetcher.ts deleted file mode 100644 index c29c51d8cc..0000000000 --- a/contracts/exchange/test/utils/simple_order_filled_cancelled_fetcher.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { AbstractOrderFilledCancelledFetcher, orderHashUtils } from '@0x/order-utils'; -import { SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; - -import { ExchangeWrapper } from './exchange_wrapper'; - -export class SimpleOrderFilledCancelledFetcher implements AbstractOrderFilledCancelledFetcher { - private readonly _exchangeWrapper: ExchangeWrapper; - constructor(exchange: ExchangeWrapper) { - this._exchangeWrapper = exchange; - } - public async getFilledTakerAmountAsync(orderHash: string): Promise { - const filledTakerAmount = new BigNumber(await this._exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash)); - return filledTakerAmount; - } - public async isOrderCancelledAsync(signedOrder: SignedOrder): Promise { - const orderHash = orderHashUtils.getOrderHashHex(signedOrder); - const isCancelled = await this._exchangeWrapper.isCancelledAsync(orderHash); - const orderEpoch = await this._exchangeWrapper.getOrderEpochAsync( - signedOrder.makerAddress, - signedOrder.senderAddress, - ); - const isCancelledByOrderEpoch = orderEpoch > signedOrder.salt; - return isCancelled || isCancelledByOrderEpoch; - } -}