@0x:contracts-integrations Refactored match_orders_test

This commit is contained in:
Alex Towle 2019-11-04 09:55:27 -08:00
parent f8b7b8cc28
commit 4e50b9b479
3 changed files with 818 additions and 1384 deletions

View File

@ -1,9 +1,8 @@
import { ERC1155ProxyWrapper, ERC20Wrapper, ERC721Wrapper } from '@0x/contracts-asset-proxy';
import { DevUtilsContract } from '@0x/contracts-dev-utils'; import { DevUtilsContract } from '@0x/contracts-dev-utils';
import { BlockchainBalanceStore, ExchangeContract, LocalBalanceStore } from '@0x/contracts-exchange'; import { BlockchainBalanceStore, ExchangeContract, LocalBalanceStore } from '@0x/contracts-exchange';
import { constants, ERC1155HoldingsByOwner, expect, OrderStatus } from '@0x/contracts-test-utils'; import { constants, expect, OrderStatus } from '@0x/contracts-test-utils';
import { orderHashUtils } from '@0x/order-utils'; import { orderHashUtils } from '@0x/order-utils';
import { AssetProxyId, BatchMatchedFillResults, FillResults, MatchedFillResults, SignedOrder } from '@0x/types'; import { BatchMatchedFillResults, FillResults, MatchedFillResults, SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import { LogWithDecodedArgs, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { LogWithDecodedArgs, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import * as _ from 'lodash'; import * as _ from 'lodash';
@ -12,13 +11,6 @@ import { DeploymentManager } from './deployment_manager';
const ZERO = new BigNumber(0); const ZERO = new BigNumber(0);
export interface IndividualERC1155Holdings {
fungible: {
[tokenId: string]: BigNumber;
};
nonFungible: BigNumber[];
}
export interface FillEventArgs { export interface FillEventArgs {
orderHash: string; orderHash: string;
makerAddress: string; makerAddress: string;
@ -27,6 +19,7 @@ export interface FillEventArgs {
takerAssetFilledAmount: BigNumber; takerAssetFilledAmount: BigNumber;
makerFeePaid: BigNumber; makerFeePaid: BigNumber;
takerFeePaid: BigNumber; takerFeePaid: BigNumber;
protocolFeePaid: BigNumber;
} }
export interface MatchTransferAmounts { export interface MatchTransferAmounts {
@ -35,17 +28,18 @@ export interface MatchTransferAmounts {
rightMakerAssetSoldByRightMakerAmount: BigNumber; // rightMakerAssetBoughtByLeftMakerAmount if omitted. rightMakerAssetSoldByRightMakerAmount: BigNumber; // rightMakerAssetBoughtByLeftMakerAmount if omitted.
rightMakerAssetBoughtByLeftMakerAmount: BigNumber; // rightMakerAssetSoldByRightMakerAmount if omitted. rightMakerAssetBoughtByLeftMakerAmount: BigNumber; // rightMakerAssetSoldByRightMakerAmount if omitted.
leftMakerAssetBoughtByRightMakerAmount: BigNumber; // leftMakerAssetSoldByLeftMakerAmount if omitted. leftMakerAssetBoughtByRightMakerAmount: BigNumber; // leftMakerAssetSoldByLeftMakerAmount if omitted.
// Taker profit. // Taker profit.
leftMakerAssetReceivedByTakerAmount: BigNumber; // 0 if omitted. leftMakerAssetReceivedByTakerAmount: BigNumber; // 0 if omitted.
rightMakerAssetReceivedByTakerAmount: BigNumber; // 0 if omitted. rightMakerAssetReceivedByTakerAmount: BigNumber; // 0 if omitted.
// Maker fees. // Maker fees.
leftMakerFeeAssetPaidByLeftMakerAmount: BigNumber; // 0 if omitted. leftMakerFeeAssetPaidByLeftMakerAmount: BigNumber; // 0 if omitted.
rightMakerFeeAssetPaidByRightMakerAmount: BigNumber; // 0 if omitted. rightMakerFeeAssetPaidByRightMakerAmount: BigNumber; // 0 if omitted.
// Taker fees. // Taker fees.
leftTakerFeeAssetPaidByTakerAmount: BigNumber; // 0 if omitted. leftTakerFeeAssetPaidByTakerAmount: BigNumber; // 0 if omitted.
rightTakerFeeAssetPaidByTakerAmount: BigNumber; // 0 if omitted. rightTakerFeeAssetPaidByTakerAmount: BigNumber; // 0 if omitted.
// Protocol fees.
leftProtocolFeePaidByTakerAmount: BigNumber; // 0 if omitted
rightProtocolFeePaidByTakerAmount: BigNumber; // 0 if omitted
} }
export interface MatchResults { export interface MatchResults {
@ -60,31 +54,6 @@ export interface BatchMatchResults {
rightFilledResults: FillEventArgs[]; rightFilledResults: FillEventArgs[];
} }
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 BatchMatchedOrders { export interface BatchMatchedOrders {
leftOrders: SignedOrder[]; leftOrders: SignedOrder[];
rightOrders: SignedOrder[]; rightOrders: SignedOrder[];
@ -100,21 +69,18 @@ export interface MatchedOrders {
} }
export class MatchOrderTester { export class MatchOrderTester {
// FIXME - Should this be public private readonly _devUtils: DevUtilsContract;
public deployment: DeploymentManager; private readonly _deployment: DeploymentManager;
protected _devUtils: DevUtilsContract;
private readonly _blockchainBalanceStore: BlockchainBalanceStore; private readonly _blockchainBalanceStore: BlockchainBalanceStore;
private readonly _localBalanceStore: LocalBalanceStore;
constructor( constructor(
deployment: DeploymentManager, deployment: DeploymentManager,
devUtils: DevUtilsContract, devUtils: DevUtilsContract,
blockchainBalanceStore: BlockchainBalanceStore, blockchainBalanceStore: BlockchainBalanceStore,
) { ) {
this.deployment = deployment; this._deployment = deployment;
this._devUtils = devUtils; this._devUtils = devUtils;
this._blockchainBalanceStore = blockchainBalanceStore; this._blockchainBalanceStore = blockchainBalanceStore;
this._localBalanceStore = LocalBalanceStore.create(blockchainBalanceStore);
} }
/** /**
@ -133,10 +99,10 @@ export class MatchOrderTester {
public async batchMatchOrdersAndAssertEffectsAsync( public async batchMatchOrdersAndAssertEffectsAsync(
orders: BatchMatchedOrders, orders: BatchMatchedOrders,
takerAddress: string, takerAddress: string,
value: BigNumber,
matchPairs: Array<[number, number]>, matchPairs: Array<[number, number]>,
expectedTransferAmounts: Array<Partial<MatchTransferAmounts>>, expectedTransferAmounts: Array<Partial<MatchTransferAmounts>>,
withMaximalFill: boolean, withMaximalFill: boolean,
initialTokenBalances?: TokenBalances,
): Promise<BatchMatchResults> { ): Promise<BatchMatchResults> {
// Ensure that the provided input is valid. // Ensure that the provided input is valid.
expect(matchPairs.length).to.be.eq(expectedTransferAmounts.length); expect(matchPairs.length).to.be.eq(expectedTransferAmounts.length);
@ -144,52 +110,56 @@ export class MatchOrderTester {
expect(orders.rightOrders.length).to.be.eq(orders.rightOrdersTakerAssetFilledAmounts.length); expect(orders.rightOrders.length).to.be.eq(orders.rightOrdersTakerAssetFilledAmounts.length);
// Ensure that the exchange is in the expected state. // Ensure that the exchange is in the expected state.
await assertBatchOrderStatesAsync(orders, this.deployment.exchange); await assertBatchOrderStatesAsync(orders, this._deployment.exchange);
// Get the token balances before executing `batchMatchOrders()`. // Update the blockchain balance store and create a new local balance store
// with the same initial balances.
await this._blockchainBalanceStore.updateBalancesAsync(); await this._blockchainBalanceStore.updateBalancesAsync();
const localBalanceStore = LocalBalanceStore.create(this._blockchainBalanceStore);
// Execute `batchMatchOrders()` // Execute `batchMatchOrders()`
let actualBatchMatchResults; let actualBatchMatchResults;
let transactionReceipt; let transactionReceipt;
if (withMaximalFill) { if (withMaximalFill) {
actualBatchMatchResults = await this.deployment.exchange.batchMatchOrdersWithMaximalFill.callAsync( actualBatchMatchResults = await this._deployment.exchange.batchMatchOrdersWithMaximalFill.callAsync(
orders.leftOrders, orders.leftOrders,
orders.rightOrders, orders.rightOrders,
orders.leftOrders.map(order => order.signature), orders.leftOrders.map(order => order.signature),
orders.rightOrders.map(order => order.signature), orders.rightOrders.map(order => order.signature),
{ from: takerAddress }, { from: takerAddress, gasPrice: constants.DEFAULT_GAS_PRICE, value },
); );
transactionReceipt = await this.deployment.exchange.batchMatchOrdersWithMaximalFill.awaitTransactionSuccessAsync( transactionReceipt = await this._deployment.exchange.batchMatchOrdersWithMaximalFill.awaitTransactionSuccessAsync(
orders.leftOrders, orders.leftOrders,
orders.rightOrders, orders.rightOrders,
orders.leftOrders.map(order => order.signature), orders.leftOrders.map(order => order.signature),
orders.rightOrders.map(order => order.signature), orders.rightOrders.map(order => order.signature),
{ from: takerAddress }, { from: takerAddress, gasPrice: constants.DEFAULT_GAS_PRICE, value },
); );
} else { } else {
actualBatchMatchResults = await this.deployment.exchange.batchMatchOrders.callAsync( actualBatchMatchResults = await this._deployment.exchange.batchMatchOrders.callAsync(
orders.leftOrders, orders.leftOrders,
orders.rightOrders, orders.rightOrders,
orders.leftOrders.map(order => order.signature), orders.leftOrders.map(order => order.signature),
orders.rightOrders.map(order => order.signature), orders.rightOrders.map(order => order.signature),
{ from: takerAddress }, { from: takerAddress, gasPrice: constants.DEFAULT_GAS_PRICE, value },
); );
transactionReceipt = await this.deployment.exchange.batchMatchOrders.awaitTransactionSuccessAsync( transactionReceipt = await this._deployment.exchange.batchMatchOrders.awaitTransactionSuccessAsync(
orders.leftOrders, orders.leftOrders,
orders.rightOrders, orders.rightOrders,
orders.leftOrders.map(order => order.signature), orders.leftOrders.map(order => order.signature),
orders.rightOrders.map(order => order.signature), orders.rightOrders.map(order => order.signature),
{ from: takerAddress }, { from: takerAddress, gasPrice: constants.DEFAULT_GAS_PRICE, value },
); );
} }
localBalanceStore.burnGas(takerAddress, constants.DEFAULT_GAS_PRICE * transactionReceipt.gasUsed);
// Simulate the batch order match. // Simulate the batch order match.
const expectedBatchMatchResults = await simulateBatchMatchOrdersAsync( const expectedBatchMatchResults = await simulateBatchMatchOrdersAsync(
orders, orders,
takerAddress, takerAddress,
this._deployment.staking.stakingProxy.address,
this._blockchainBalanceStore, this._blockchainBalanceStore,
this._localBalanceStore, localBalanceStore,
matchPairs, matchPairs,
expectedTransferAmounts, expectedTransferAmounts,
this._devUtils, this._devUtils,
@ -202,8 +172,8 @@ export class MatchOrderTester {
expectedBatchMatchResults, expectedBatchMatchResults,
transactionReceipt, transactionReceipt,
this._blockchainBalanceStore, this._blockchainBalanceStore,
this._localBalanceStore, localBalanceStore,
this.deployment.exchange, this._deployment.exchange,
); );
return expectedBatchMatchResults; return expectedBatchMatchResults;
} }
@ -221,56 +191,63 @@ export class MatchOrderTester {
*/ */
public async matchOrdersAndAssertEffectsAsync( public async matchOrdersAndAssertEffectsAsync(
orders: MatchedOrders, orders: MatchedOrders,
takerAddress: string,
expectedTransferAmounts: Partial<MatchTransferAmounts>, expectedTransferAmounts: Partial<MatchTransferAmounts>,
takerAddress: string,
value: BigNumber,
withMaximalFill: boolean, withMaximalFill: boolean,
initialTokenBalances?: TokenBalances,
): Promise<MatchResults> { ): Promise<MatchResults> {
await assertInitialOrderStatesAsync(orders, this.deployment.exchange); await assertInitialOrderStatesAsync(orders, this._deployment.exchange);
// Update the blockchain balance store and create a new local balance store
// with the same initial balances.
await this._blockchainBalanceStore.updateBalancesAsync();
const localBalanceStore = LocalBalanceStore.create(this._blockchainBalanceStore);
// Execute `matchOrders()` // Execute `matchOrders()`
let actualMatchResults; let actualMatchResults;
let transactionReceipt; let transactionReceipt;
if (withMaximalFill) { if (withMaximalFill) {
actualMatchResults = await this.deployment.exchange.matchOrdersWithMaximalFill.callAsync( actualMatchResults = await this._deployment.exchange.matchOrdersWithMaximalFill.callAsync(
orders.leftOrder, orders.leftOrder,
orders.rightOrder, orders.rightOrder,
orders.leftOrder.signature, orders.leftOrder.signature,
orders.rightOrder.signature, orders.rightOrder.signature,
{ from: takerAddress }, { from: takerAddress, gasPrice: constants.DEFAULT_GAS_PRICE, value },
); );
transactionReceipt = await this.deployment.exchange.matchOrdersWithMaximalFill.awaitTransactionSuccessAsync( transactionReceipt = await this._deployment.exchange.matchOrdersWithMaximalFill.awaitTransactionSuccessAsync(
orders.leftOrder, orders.leftOrder,
orders.rightOrder, orders.rightOrder,
orders.leftOrder.signature, orders.leftOrder.signature,
orders.rightOrder.signature, orders.rightOrder.signature,
{ from: takerAddress }, { from: takerAddress, gasPrice: constants.DEFAULT_GAS_PRICE, value },
); );
} else { } else {
actualMatchResults = await this.deployment.exchange.matchOrders.callAsync( actualMatchResults = await this._deployment.exchange.matchOrders.callAsync(
orders.leftOrder, orders.leftOrder,
orders.rightOrder, orders.rightOrder,
orders.leftOrder.signature, orders.leftOrder.signature,
orders.rightOrder.signature, orders.rightOrder.signature,
{ from: takerAddress }, { from: takerAddress, gasPrice: constants.DEFAULT_GAS_PRICE, value },
); );
transactionReceipt = await this.deployment.exchange.matchOrders.awaitTransactionSuccessAsync( transactionReceipt = await this._deployment.exchange.matchOrders.awaitTransactionSuccessAsync(
orders.leftOrder, orders.leftOrder,
orders.rightOrder, orders.rightOrder,
orders.leftOrder.signature, orders.leftOrder.signature,
orders.rightOrder.signature, orders.rightOrder.signature,
{ from: takerAddress }, { from: takerAddress, gasPrice: constants.DEFAULT_GAS_PRICE, value },
); );
} }
localBalanceStore.burnGas(takerAddress, constants.DEFAULT_GAS_PRICE * transactionReceipt.gasUsed);
// Simulate the fill. // Simulate the fill.
const expectedMatchResults = await simulateMatchOrdersAsync( const expectedMatchResults = await simulateMatchOrdersAsync(
orders, orders,
takerAddress, takerAddress,
this._deployment.staking.stakingProxy.address,
this._blockchainBalanceStore, this._blockchainBalanceStore,
toFullMatchTransferAmounts(expectedTransferAmounts), toFullMatchTransferAmounts(expectedTransferAmounts),
this._devUtils, this._devUtils,
this._localBalanceStore, localBalanceStore,
); );
const expectedResults = convertToMatchResults(expectedMatchResults); const expectedResults = convertToMatchResults(expectedMatchResults);
expect(actualMatchResults).to.be.eql(expectedResults); expect(actualMatchResults).to.be.eql(expectedResults);
@ -279,9 +256,9 @@ export class MatchOrderTester {
await assertMatchResultsAsync( await assertMatchResultsAsync(
expectedMatchResults, expectedMatchResults,
transactionReceipt, transactionReceipt,
this._localBalanceStore, localBalanceStore,
this._blockchainBalanceStore, this._blockchainBalanceStore,
this.deployment.exchange, this._deployment.exchange,
); );
return expectedMatchResults; return expectedMatchResults;
} }
@ -321,6 +298,10 @@ function toFullMatchTransferAmounts(partial: Partial<MatchTransferAmounts>): Mat
partial.leftTakerFeeAssetPaidByTakerAmount || ZERO, partial.leftTakerFeeAssetPaidByTakerAmount || ZERO,
rightTakerFeeAssetPaidByTakerAmount: rightTakerFeeAssetPaidByTakerAmount:
partial.rightTakerFeeAssetPaidByTakerAmount || ZERO, partial.rightTakerFeeAssetPaidByTakerAmount || ZERO,
leftProtocolFeePaidByTakerAmount:
partial.leftProtocolFeePaidByTakerAmount || ZERO,
rightProtocolFeePaidByTakerAmount:
partial.rightProtocolFeePaidByTakerAmount || ZERO,
}; };
} }
@ -336,6 +317,7 @@ function toFullMatchTransferAmounts(partial: Partial<MatchTransferAmounts>): Mat
async function simulateBatchMatchOrdersAsync( async function simulateBatchMatchOrdersAsync(
orders: BatchMatchedOrders, orders: BatchMatchedOrders,
takerAddress: string, takerAddress: string,
stakingProxyAddress: string,
blockchainBalanceStore: BlockchainBalanceStore, blockchainBalanceStore: BlockchainBalanceStore,
localBalanceStore: LocalBalanceStore, localBalanceStore: LocalBalanceStore,
matchPairs: Array<[number, number]>, matchPairs: Array<[number, number]>,
@ -401,6 +383,7 @@ async function simulateBatchMatchOrdersAsync(
await simulateMatchOrdersAsync( await simulateMatchOrdersAsync(
matchedOrders, matchedOrders,
takerAddress, takerAddress,
stakingProxyAddress,
blockchainBalanceStore, blockchainBalanceStore,
toFullMatchTransferAmounts(transferAmounts[i]), toFullMatchTransferAmounts(transferAmounts[i]),
devUtils, devUtils,
@ -464,6 +447,7 @@ async function simulateBatchMatchOrdersAsync(
async function simulateMatchOrdersAsync( async function simulateMatchOrdersAsync(
orders: MatchedOrders, orders: MatchedOrders,
takerAddress: string, takerAddress: string,
stakingProxyAddress: string,
blockchainBalanceStore: BlockchainBalanceStore, // FIXME - Is this right? blockchainBalanceStore: BlockchainBalanceStore, // FIXME - Is this right?
transferAmounts: MatchTransferAmounts, transferAmounts: MatchTransferAmounts,
devUtils: DevUtilsContract, devUtils: DevUtilsContract,
@ -485,6 +469,7 @@ async function simulateMatchOrdersAsync(
}, },
fills: simulateFillEvents(orders, takerAddress, transferAmounts), fills: simulateFillEvents(orders, takerAddress, transferAmounts),
}; };
// Right maker asset -> left maker // Right maker asset -> left maker
localBalanceStore.transferAsset( localBalanceStore.transferAsset(
orders.rightOrder.makerAddress, orders.rightOrder.makerAddress,
@ -492,6 +477,7 @@ async function simulateMatchOrdersAsync(
transferAmounts.rightMakerAssetBoughtByLeftMakerAmount, transferAmounts.rightMakerAssetBoughtByLeftMakerAmount,
orders.rightOrder.makerAssetData, orders.rightOrder.makerAssetData,
); );
// FIXME - Is this a necessary condition? // FIXME - Is this a necessary condition?
if (orders.leftOrder.makerAddress !== orders.leftOrder.feeRecipientAddress) { if (orders.leftOrder.makerAddress !== orders.leftOrder.feeRecipientAddress) {
// Left maker fees // Left maker fees
@ -502,6 +488,8 @@ async function simulateMatchOrdersAsync(
orders.leftOrder.makerFeeAssetData, orders.leftOrder.makerFeeAssetData,
); );
} }
// FIXME - Is this a necessary condition?
// Left maker asset -> right maker // Left maker asset -> right maker
localBalanceStore.transferAsset( localBalanceStore.transferAsset(
orders.leftOrder.makerAddress, orders.leftOrder.makerAddress,
@ -509,6 +497,7 @@ async function simulateMatchOrdersAsync(
transferAmounts.leftMakerAssetBoughtByRightMakerAmount, transferAmounts.leftMakerAssetBoughtByRightMakerAmount,
orders.leftOrder.makerAssetData, orders.leftOrder.makerAssetData,
); );
// FIXME - Is this a necessary condition? // FIXME - Is this a necessary condition?
if (orders.rightOrder.makerAddress !== orders.rightOrder.feeRecipientAddress) { if (orders.rightOrder.makerAddress !== orders.rightOrder.feeRecipientAddress) {
// Right maker fees // Right maker fees
@ -519,6 +508,7 @@ async function simulateMatchOrdersAsync(
orders.rightOrder.makerFeeAssetData, orders.rightOrder.makerFeeAssetData,
); );
} }
// Left taker profit // Left taker profit
localBalanceStore.transferAsset( localBalanceStore.transferAsset(
orders.leftOrder.makerAddress, orders.leftOrder.makerAddress,
@ -526,6 +516,7 @@ async function simulateMatchOrdersAsync(
transferAmounts.leftMakerAssetReceivedByTakerAmount, transferAmounts.leftMakerAssetReceivedByTakerAmount,
orders.leftOrder.makerAssetData, orders.leftOrder.makerAssetData,
); );
// Right taker profit // Right taker profit
localBalanceStore.transferAsset( localBalanceStore.transferAsset(
orders.rightOrder.makerAddress, orders.rightOrder.makerAddress,
@ -533,6 +524,7 @@ async function simulateMatchOrdersAsync(
transferAmounts.rightMakerAssetReceivedByTakerAmount, transferAmounts.rightMakerAssetReceivedByTakerAmount,
orders.rightOrder.makerAssetData, orders.rightOrder.makerAssetData,
); );
// Left taker fees // Left taker fees
localBalanceStore.transferAsset( localBalanceStore.transferAsset(
takerAddress, takerAddress,
@ -540,6 +532,7 @@ async function simulateMatchOrdersAsync(
transferAmounts.leftTakerFeeAssetPaidByTakerAmount, transferAmounts.leftTakerFeeAssetPaidByTakerAmount,
orders.leftOrder.takerFeeAssetData, orders.leftOrder.takerFeeAssetData,
); );
// Right taker fees // Right taker fees
localBalanceStore.transferAsset( localBalanceStore.transferAsset(
takerAddress, takerAddress,
@ -548,6 +541,13 @@ async function simulateMatchOrdersAsync(
orders.rightOrder.takerFeeAssetData, orders.rightOrder.takerFeeAssetData,
); );
// Protocol Fee
localBalanceStore.sendEth(
takerAddress,
stakingProxyAddress,
transferAmounts.leftProtocolFeePaidByTakerAmount.plus(transferAmounts.rightProtocolFeePaidByTakerAmount),
);
return matchResults; return matchResults;
} }
@ -574,6 +574,9 @@ async function assertBatchMatchResultsAsync(
transactionReceipt, transactionReceipt,
); );
// Update the blockchain balance store balances.
await blockchainBalanceStore.updateBalancesAsync();
// Ensure that the actual and expected token balances are equivalent. // Ensure that the actual and expected token balances are equivalent.
localBalanceStore.assertEquals(blockchainBalanceStore); localBalanceStore.assertEquals(blockchainBalanceStore);
@ -598,6 +601,9 @@ async function assertMatchResultsAsync(
// Check the fill events. // Check the fill events.
assertFillEvents(matchResults.fills, transactionReceipt); assertFillEvents(matchResults.fills, transactionReceipt);
// Update the blockchain balance store balances.
await blockchainBalanceStore.updateBalancesAsync();
// Check the token balances. // Check the token balances.
localBalanceStore.assertEquals(blockchainBalanceStore); localBalanceStore.assertEquals(blockchainBalanceStore);
@ -654,6 +660,7 @@ function simulateFillEvents(
takerAssetFilledAmount: transferAmounts.rightMakerAssetBoughtByLeftMakerAmount, takerAssetFilledAmount: transferAmounts.rightMakerAssetBoughtByLeftMakerAmount,
makerFeePaid: transferAmounts.leftMakerFeeAssetPaidByLeftMakerAmount, makerFeePaid: transferAmounts.leftMakerFeeAssetPaidByLeftMakerAmount,
takerFeePaid: transferAmounts.leftTakerFeeAssetPaidByTakerAmount, takerFeePaid: transferAmounts.leftTakerFeeAssetPaidByTakerAmount,
protocolFeePaid: transferAmounts.leftProtocolFeePaidByTakerAmount,
}, },
// Right order Fill // Right order Fill
{ {
@ -664,10 +671,12 @@ function simulateFillEvents(
takerAssetFilledAmount: transferAmounts.leftMakerAssetBoughtByRightMakerAmount, takerAssetFilledAmount: transferAmounts.leftMakerAssetBoughtByRightMakerAmount,
makerFeePaid: transferAmounts.rightMakerFeeAssetPaidByRightMakerAmount, makerFeePaid: transferAmounts.rightMakerFeeAssetPaidByRightMakerAmount,
takerFeePaid: transferAmounts.rightTakerFeeAssetPaidByTakerAmount, takerFeePaid: transferAmounts.rightTakerFeeAssetPaidByTakerAmount,
protocolFeePaid: transferAmounts.rightProtocolFeePaidByTakerAmount,
}, },
]; ];
} }
// FIXME - Refactor this to use filterToLogsArguments
/** /**
* Extract `Fill` events from a transaction receipt. * Extract `Fill` events from a transaction receipt.
*/ */
@ -680,6 +689,7 @@ function extractFillEventsfromReceipt(receipt: TransactionReceiptWithDecodedLogs
takerAssetFilledAmount: string; takerAssetFilledAmount: string;
makerFeePaid: string; makerFeePaid: string;
takerFeePaid: string; takerFeePaid: string;
protocolFeePaid: string;
} }
const actualFills = (_.filter(receipt.logs, ['event', 'Fill']) as any) as Array< const actualFills = (_.filter(receipt.logs, ['event', 'Fill']) as any) as Array<
LogWithDecodedArgs<RawFillEventArgs> LogWithDecodedArgs<RawFillEventArgs>
@ -693,18 +703,10 @@ function extractFillEventsfromReceipt(receipt: TransactionReceiptWithDecodedLogs
takerAssetFilledAmount: new BigNumber(fill.args.takerAssetFilledAmount), takerAssetFilledAmount: new BigNumber(fill.args.takerAssetFilledAmount),
makerFeePaid: new BigNumber(fill.args.makerFeePaid), makerFeePaid: new BigNumber(fill.args.makerFeePaid),
takerFeePaid: new BigNumber(fill.args.takerFeePaid), takerFeePaid: new BigNumber(fill.args.takerFeePaid),
protocolFeePaid: new BigNumber(fill.args.protocolFeePaid),
})); }));
} }
/**
* Asserts that all expected token holdings match the actual holdings.
* @param expectedBalances Expected balances.
* @param actualBalances Actual balances.
*/
function assertBalances(expectedBalances: TokenBalances, actualBalances: TokenBalances): void {
expect(encodeTokenBalances(actualBalances)).to.deep.equal(encodeTokenBalances(expectedBalances));
}
/** /**
* Asserts the initial exchange state for batch matched orders. * Asserts the initial exchange state for batch matched orders.
* @param orders Batch matched orders with intial filled amounts. * @param orders Batch matched orders with intial filled amounts.
@ -819,64 +821,6 @@ async function assertOrderFilledAmountAsync(
expect(actualStatus, `${side} order final status`).to.equal(expectedStatus); expect(actualStatus, `${side} order final status`).to.equal(expectedStatus);
} }
/**
* Retrieve the current token balances of all known addresses.
* @param erc20Wrapper The ERC20Wrapper instance.
* @param erc721Wrapper The ERC721Wrapper instance.
* @param erc1155Wrapper The ERC1155ProxyWrapper instance.
* @return A promise that resolves to a `TokenBalances`.
*/
export async function getTokenBalancesAsync(
erc20Wrapper: ERC20Wrapper,
erc721Wrapper: ERC721Wrapper,
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])));
}
/** /**
* Gets the last match in a BatchMatchResults object. * Gets the last match in a BatchMatchResults object.
* @param batchMatchResults The BatchMatchResults object. * @param batchMatchResults The BatchMatchResults object.
@ -886,6 +830,7 @@ function getLastMatch(batchMatchResults: BatchMatchResults): MatchResults {
return batchMatchResults.matches[batchMatchResults.matches.length - 1]; return batchMatchResults.matches[batchMatchResults.matches.length - 1];
} }
// FIXME - This can probably be removed in favor of the reference functions.
/** /**
* Add a new fill results object to a total fill results object destructively. * Add a new fill results object to a total fill results object destructively.
* @param total The total fill results that should be updated. * @param total The total fill results that should be updated.
@ -901,81 +846,7 @@ function addFillResults(total: FillEventArgs, fill: FillEventArgs): void {
total.takerAssetFilledAmount = total.takerAssetFilledAmount.plus(fill.takerAssetFilledAmount); total.takerAssetFilledAmount = total.takerAssetFilledAmount.plus(fill.takerAssetFilledAmount);
total.makerFeePaid = total.makerFeePaid.plus(fill.makerFeePaid); total.makerFeePaid = total.makerFeePaid.plus(fill.makerFeePaid);
total.takerFeePaid = total.takerFeePaid.plus(fill.takerFeePaid); total.takerFeePaid = total.takerFeePaid.plus(fill.takerFeePaid);
} total.protocolFeePaid = total.protocolFeePaid.plus(fill.protocolFeePaid);
/**
* Takes a `totalBalances`, a `balances`, and an `initialBalances`, subtracts the `initialBalances
* from the `balances`, and then adds the result to `totalBalances`.
* @param totalBalances A set of balances to be updated with new results.
* @param balances A new set of results that deviate from the `initialBalances` by one matched
* order. Subtracting away the `initialBalances` leaves behind a diff of the
* matched orders effect on the `initialBalances`.
* @param initialBalances The token balances from before the call to `batchMatchOrders()`.
* @return The updated total balances using the derived balance difference.
*/
function aggregateBalances(
totalBalances: TokenBalances,
balances: TokenBalances,
initialBalances: TokenBalances,
): TokenBalances {
// ERC20
for (const owner of _.keys(totalBalances.erc20)) {
for (const contract of _.keys(totalBalances.erc20[owner])) {
const difference = balances.erc20[owner][contract].minus(initialBalances.erc20[owner][contract]);
totalBalances.erc20[owner][contract] = totalBalances.erc20[owner][contract].plus(difference);
}
}
// ERC721
for (const owner of _.keys(totalBalances.erc721)) {
for (const contract of _.keys(totalBalances.erc721[owner])) {
totalBalances.erc721[owner][contract] = _.zipWith(
totalBalances.erc721[owner][contract],
balances.erc721[owner][contract],
initialBalances.erc721[owner][contract],
(a: BigNumber, b: BigNumber, c: BigNumber) => a.plus(b.minus(c)),
);
}
}
// ERC1155
for (const owner of _.keys(totalBalances.erc1155)) {
for (const contract of _.keys(totalBalances.erc1155[owner])) {
// Fungible
for (const tokenId of _.keys(totalBalances.erc1155[owner][contract].fungible)) {
const difference = balances.erc1155[owner][contract].fungible[tokenId].minus(
initialBalances.erc1155[owner][contract].fungible[tokenId],
);
totalBalances.erc1155[owner][contract].fungible[tokenId] = totalBalances.erc1155[owner][
contract
].fungible[tokenId].plus(difference);
}
// Nonfungible
let isDuplicate = false;
for (const value of balances.erc1155[owner][contract].nonFungible) {
// If the value is in the initial balances or the total balances, skip the
// value since it will already be added.
for (const val of totalBalances.erc1155[owner][contract].nonFungible) {
if (value.isEqualTo(val)) {
isDuplicate = true;
}
}
if (!isDuplicate) {
for (const val of initialBalances.erc1155[owner][contract].nonFungible) {
if (value.isEqualTo(val)) {
isDuplicate = true;
}
}
}
if (!isDuplicate) {
totalBalances.erc1155[owner][contract].nonFungible.push(value);
}
isDuplicate = false;
}
}
}
return totalBalances;
} }
/** /**
@ -1042,14 +913,14 @@ function convertToMatchResults(result: MatchResults): MatchedFillResults {
takerAssetFilledAmount: result.fills[0].takerAssetFilledAmount, takerAssetFilledAmount: result.fills[0].takerAssetFilledAmount,
makerFeePaid: result.fills[0].makerFeePaid, makerFeePaid: result.fills[0].makerFeePaid,
takerFeePaid: result.fills[0].takerFeePaid, takerFeePaid: result.fills[0].takerFeePaid,
protocolFeePaid: constants.ZERO_AMOUNT, protocolFeePaid: result.fills[0].protocolFeePaid,
}, },
right: { right: {
makerAssetFilledAmount: result.fills[1].makerAssetFilledAmount, makerAssetFilledAmount: result.fills[1].makerAssetFilledAmount,
takerAssetFilledAmount: result.fills[1].takerAssetFilledAmount, takerAssetFilledAmount: result.fills[1].takerAssetFilledAmount,
makerFeePaid: result.fills[1].makerFeePaid, makerFeePaid: result.fills[1].makerFeePaid,
takerFeePaid: result.fills[1].takerFeePaid, takerFeePaid: result.fills[1].takerFeePaid,
protocolFeePaid: constants.ZERO_AMOUNT, protocolFeePaid: result.fills[1].protocolFeePaid,
}, },
profitInLeftMakerAsset, profitInLeftMakerAsset,
profitInRightMakerAsset, profitInRightMakerAsset,
@ -1068,7 +939,7 @@ function convertToFillResults(result: FillEventArgs): FillResults {
takerAssetFilledAmount: result.takerAssetFilledAmount, takerAssetFilledAmount: result.takerAssetFilledAmount,
makerFeePaid: result.makerFeePaid, makerFeePaid: result.makerFeePaid,
takerFeePaid: result.takerFeePaid, takerFeePaid: result.takerFeePaid,
protocolFeePaid: constants.ZERO_AMOUNT, protocolFeePaid: result.protocolFeePaid,
}; };
return fillResults; return fillResults;
} }
@ -1082,10 +953,11 @@ function emptyFillEventArgs(): FillEventArgs {
orderHash: '', orderHash: '',
makerAddress: '', makerAddress: '',
takerAddress: '', takerAddress: '',
makerAssetFilledAmount: new BigNumber(0), makerAssetFilledAmount: constants.ZERO_AMOUNT,
takerAssetFilledAmount: new BigNumber(0), takerAssetFilledAmount: constants.ZERO_AMOUNT,
makerFeePaid: new BigNumber(0), makerFeePaid: constants.ZERO_AMOUNT,
takerFeePaid: new BigNumber(0), takerFeePaid: constants.ZERO_AMOUNT,
protocolFeePaid: constants.ZERO_AMOUNT,
}; };
return empty; return empty;
} }

View File

@ -110,6 +110,7 @@ export function assertIntegerRoughlyEquals(actual: Numberish, expected: Numberis
*/ */
export function toBaseUnitAmount(amount: Numberish, decimals?: number): BigNumber { export function toBaseUnitAmount(amount: Numberish, decimals?: number): BigNumber {
const amountAsBigNumber = new BigNumber(amount); const amountAsBigNumber = new BigNumber(amount);
const baseUnitAmount = Web3Wrapper.toBaseUnitAmount(amountAsBigNumber, decimals || 18); const baseDecimals = decimals !== undefined ? decimals : 18;
const baseUnitAmount = Web3Wrapper.toBaseUnitAmount(amountAsBigNumber, baseDecimals);
return baseUnitAmount; return baseUnitAmount;
} }